commit b1069b9afd7da8d67c53eecfced3aa726f6ecb45
Author: jingquan huang
+ <%= @exercise.try(:exercise_description).nil? ? "" : @exercise.try(:exercise_description).html_safe %>
+
+
+ <%= convert_to_char((index+1).to_s)%><%= s.choice_text%>
+
+
+ <%= convert_to_char((index+1).to_s)%><%= s.choice_text%>
+
+ <% q.exercise_choices.each_with_index do |s,index| %>
+
+
+ <%= s.choice_text %>
+
+ <% end %>
+
+ 答案(填空<%= index+1 %>):
+
+
+
+
+ 第<%= index+1 %>关
+ <%= c.challenge.subject %>
+
+ <%= c&.question_score %> 分
+
+
+ <%= @exercise.try(:exercise_description).nil? ? "" : @exercise.try(:exercise_description).html_safe %>
+
+ 客观题
+ 正确
+ 错误
+ 部分得分
+ 总分:<%= @exercise_user.score %>分
+
+ 主观题
+ 已评
+ 未评
+ 开始答题时间:<%= @exercise_user.start_at.present? ? @exercise_user.start_at.strftime("%Y-%m-%d %H:%M") : "--" %>
+
+
+ <%= convert_to_char((index+1).to_s)%><%= s.choice_text%>
+
+ <% if check_answer %>
+
+ <% else %>
+
+ <% end %>
+ <%= convert_to_char((index+1).to_s)%><%= s.choice_text%>
+
+ <% q.exercise_choices.each do |s| %>
+ <% if user_answer.present? && (s.id == user_answer.first.exercise_choice_id) %>
+ <% check_answer = 'choose-answer' %>
+ <% else %>
+ <% check_answer = '' %>
+ <% end %>
+
+
+ <%= s.choice_text %>
+
+ <% end %>
+
+ 答案(填空<%= index+1 %>):
+ <%= check_answer.html_safe %>
+
+ <%= check_answer.html_safe %>
+
+ 阶段成绩
+
+ 实训详情
+
+ 第<%= index+1 %>关<%= game.challenge.subject %>
+
+
+ 最近通过的代码
+ <%= game.challenge.path %>
+
+ <%= ques_user.real_name %><%= ques_comment.first.updated_at.strftime('%Y-%m-%d %H:%M') %> <%= ques_comment.first.comment %>
+ <%= @course.name %> >
+ <%= @homework.course_second_category&.name || '实训作业'%> >
+ <% homework_ids = @homework.course.homework_commons.where(homework_type: 4, course_homework_category_id: @homework.course_homework_category_id)
+ .order(Arel.sql('IF(ISNULL(publish_time),0,1),publish_time DESC, created_at DESC')).pluck(:id) %>
+ #<%= homework_ids.index(@homework.id) + 1 %>>
+ <%= @user.real_name %>
+
+
+ <%= @shixun.name %>
+
+ 总体评价
+
+
+ 个人总结
+
+ 阶段成绩
+
+ 图形统计
+
+ 实训详情
+
+ 第<%= index+1 %>关<%= game.challenge.subject %>
+
+ 最近通过的代码
+ <%= notice %> 说明:该界面适用于存储全局变量 <%= notice %>
+ Name:
+ <%= @edu_setting.name %>
+
+ Value:
+ <%= @edu_setting.value %>
+
+ <%= @exercise.try(:exercise_description).nil? ? "" : @exercise.try(:exercise_description).html_safe %>
+
+ 客观题
+ 正确
+ 错误
+ 总分:<%= @exercise.exercise_users.where(user_id:31798).first.score %>分
+
+ 主观题
+ 已评
+ 未评
+ 开始答题时间:<%= @exercise_user.start_at.present? ? @exercise_user.start_at.strftime("%Y-%m-%d %H:%M") : "--" %>
+
+
+ <%= convert_to_char((index+1).to_s)%><%= s.choice_text%>
+
+ <% if check_answer %>
+
+ <% else %>
+
+ <% end %>
+ <%= convert_to_char((index+1).to_s)%><%= s.choice_text%>
+
+ <% q.exercise_choices.each_with_index do |s,index| %>
+ <% check_answer = (user_answer.present? && (s.id == user_answer.first.exercise_choice_id)) ? "choose-answer" : '' %>
+
+
+ <%= s.choice_text %>
+
+ <% end %>
+
+ 答案(填空<%= index+1 %>):
+ <%= check_answer.html_safe %>
+
+ <%= check_answer.html_safe %>
+
+ 阶段成绩
+
+ 实训详情
+
+ 第<%= index+1 %>关<%= game.challenge.subject %>
+
+
+ 最近通过的代码
+ <%= game.challenge.path %>
+
+ <%= c %> <%= notice %> <%= notice %>
+ Name:
+ <%= @tem_test.name %>
+
+ Email:
+ <%= @tem_test.email %>
+
+ 您好!
+
+ 您正在注册Educoder,请在10分钟内在注册页输入此验证码,并进行下一步操作。
+ 如非你本人操作,请忽略此邮件。
+ <%= @code %>
+ 如果您并未发过此请求,则可能是因为其他用户在注册时误输了您的邮件地址,而使您收到了这封邮件,那么您可以放心的忽略此邮件,无需进一步采取任何操作。
+
+ 您好!
+
+ 您正在注册Educoder,请在10分钟内在注册页输入此验证码,并进行下一步操作。
+ 如非你本人操作,请忽略此邮件。
+ <%= @code %>
+ 如果您并未发过此请求,则可能是因为其他用户在注册时误输了您的邮件地址,而使您收到了这封邮件,那么您可以放心的忽略此邮件,无需进一步采取任何操作。
+ You may have mistyped the address or the page may have moved. If you are the application owner check the logs for more information. Maybe you tried to change something you didn't have access to. If you are the application owner check the logs for more information. If you are the application owner check the logs for more information.
').gsub(/\t/, " \; \; \; \; \; \; \; \;") if @qurey_test_sets.first['out_put'].present?
+ else
+ @qurey_test_sets = TestSet.find_by_sql("SELECT t.is_public, t.input, t.output, t.position
+ FROM test_sets t where t.challenge_id = #{challenge.id}")
+ end
+ end
+
+ # 实训选择题需要局部刷新或者显示的部分
+ def choose_container game_challenge, game, max_query_index
+ # category 1: 单选题,其它的多选题(目前只有两种)
+ challenge_chooses = game_challenge.challenge_chooses.includes(:challenge_questions)
+ test_sets = []
+ @chooses = []
+
+ # 选择题测试集统计
+ challenge_chooses_count = challenge_chooses.count
+ choose_correct_num = game.choose_correct_num(max_query_index)
+ game_outputs = game.outputs.where(:query_index => max_query_index)
+
+ # 判断用户是否有提交
+ had_submmit = game_outputs.present?
+
+ # 判断选择题是否写了标准答案
+ has_answer = []
+ challenge_chooses.each do |choose|
+ challenge_question = []
+ output = game_outputs.select{|game_output| game_output.test_set_position == choose.position}[0] unless game_outputs.blank?
+
+ category = choose.category
+ subject = choose.subject
+ choose.challenge_questions.each do |question|
+ position = question.position
+ option_name = question.option_name
+ challenge_question << {:positon => position, :option_name => option_name}
+ end
+ # actual_output为空表示暂时没有评测答题,不允许查看
+ actual_output = output.try(:actual_output).try(:strip)
+ has_answer << choose.answer if choose.answer.present?
+ # 标准答案处理,错误的不让用户查看,用-1替代
+ standard_answer = (actual_output.blank? || !output.try(:result)) ? -1 : choose.standard_answer
+ result = output.try(:result)
+ sin_test_set = {:result => result, :actual_output => actual_output, :standard_answer => standard_answer,
+ :position => choose.position}
+
+ sin_choose = {:category => category, :subject => subject, :challenge_question => challenge_question}
+ @chooses << sin_choose
+ test_sets << sin_test_set
+ end
+ @has_answer = has_answer.present?
+ @choose_test_cases = {:had_submmit => had_submmit, :challenge_chooses_count => challenge_chooses_count,
+ :choose_correct_num => choose_correct_num, :test_sets => test_sets}
+ end
+
+
+ def find_game
+ @game = Game.find_by_identifier(params[:identifier])
+ if @game.blank?
+ normal_status(404, "...")
+ return
+ end
+
+ @myshixun = @game.myshixun
+ end
+
+ def find_shixun
+ @shixun = Shixun.find(@myshixun.shixun_id)
+ end
+
+ # http://localhost:3000/tasks/hcie39pw2bjn
+ # 可以访问条件:学员本身;管理员;TPM制作者
+ def allowed
+ @identity = current_user.game_identity(@game)
+ raise Educoder::TipException.new(403, "..") if @identity > User::EDU_GAME_MANAGER
+ end
+end
diff --git a/app/controllers/gits_controller.rb b/app/controllers/gits_controller.rb
new file mode 100644
index 000000000..778963f6e
--- /dev/null
+++ b/app/controllers/gits_controller.rb
@@ -0,0 +1,62 @@
+class GitsController < ApplicationController
+
+ #供git-workhorse反向调用认证
+ def auth
+ # HTTP_AUTHORIZATION: "Basic 这里base64编码的的密码(user:passwd)"
+ logger.info("11111112222223333#{request.env["HTTP_AUTHORIZATION"]}")
+ #logger.info("#########-----request_env: #{request.env}")
+ # {"service"=>"git-receive-pack", "controller"=>"gits", "action"=>"auth",
+ # "url"=>"forge01/cermyt39.git/info/refs"}
+ #
+ gituser = edu_setting('git_username')
+ gitpassword = edu_setting('git_password')
+
+ result = false
+ if request.env["HTTP_AUTHORIZATION"] && request.env["HTTP_AUTHORIZATION"].split(" ").length == 2
+ username_password = Base64.decode64(request.env["HTTP_AUTHORIZATION"].split(" ")[1])
+ input_username = username_password.split(":")[0].strip()
+ input_password = username_password.split(":")[1].strip()
+ uid_logger("git start auth: input_username is #{input_username}")
+
+ # Git 超级权限用户
+ if input_username == gituser && input_password == gitpassword
+ result = true
+ else
+ # 用户是否对对象拥有权限
+ system_user = User.find_by_login(input_username) || User.find_by_mail(input_username) || User.find_by_phone(input_username)
+
+ # 如果用户名密码错误
+ if !system_user.check_password?(input_password)
+ uid_logger_error("git start: password is wrong")
+ result = false
+ else
+ git_url = params["url"]
+ username = git_url.split("/")[0]
+ shixunname = git_url.split("/")[1].split(".")[0]
+ repo_name = username + "/" + shixunname
+ uid_logger("git start: repo_name is #{repo_name}")
+ shixun = Shixun.select([:id, :user_id, :repo_name, :identifier]).where(repo_name: repo_name).first
+ uid_logger("git start auth: shixun identifier is #{shixun.try(:identifier)}")
+ uid_logger("git start auth: systemuser is #{system_user.try(:login)}")
+
+ if shixun.present?
+ if system_user.present? && system_user.manager_of_shixun?(shixun)
+ result = true
+ else
+ logger.info("git411 start")
+ result = false
+ end
+ else
+ render :json => { :status => 404 }
+ result = false
+ end
+ end
+ end
+ end
+
+ authenticate_or_request_with_http_basic do |username, password|
+ result
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/graduation_tasks_controller.rb b/app/controllers/graduation_tasks_controller.rb
new file mode 100644
index 000000000..d95716c9b
--- /dev/null
+++ b/app/controllers/graduation_tasks_controller.rb
@@ -0,0 +1,580 @@
+class GraduationTasksController < ApplicationController
+ before_action :require_login, except: [:index]
+ before_action :find_course, except: [:edit, :update, :settings, :update_settings, :tasks_list, :show, :show_comment]
+ before_action :find_task, only: [:edit, :update, :settings, :update_settings, :tasks_list, :show, :show_comment]
+ before_action :user_course_identity
+ before_action :task_publish, only: [:show, :show_comment, :tasks_list, :settings]
+ before_action :teacher_allowed, only: [:new, :create, :edit, :update, :set_public,:multi_destroy, :publish_task, :end_task,
+ :update_settings, :add_to_bank]
+ before_action :require_id_params, only: [:set_public ,:multi_destroy, :publish_task, :end_task, :add_to_bank]
+ before_action :valid_params, only: [:update_settings]
+ include ExportHelper
+
+ def index
+ search = "#{params[:search].to_s.strip.downcase}"
+ order = params[:order]
+ page = params[:page] ? params[:page].to_i : 1
+
+ default_order = "IF(ISNULL(graduation_tasks.publish_time),0,1), graduation_tasks.publish_time DESC, graduation_tasks.created_at DESC"
+ @identity = current_user.course_identity(@course)
+ if @identity < Course::STUDENT
+ @tasks = @course.graduation_tasks.where("graduation_tasks.name like ?", "%#{search}%")
+ else
+ @tasks = @course.graduation_tasks.where("graduation_tasks.name like ? and publish_time <= '#{Time.now}'", "%#{search}%")
+ end
+
+ if order.present? && order != "all"
+ if @course.is_end
+ @tasks = @tasks.none
+ elsif order.to_i == 4 # 补交
+ @tasks = @tasks.where("status > 1 and allow_late = 1 and (late_time is null or late_time > '#{Time.now}')")
+ else
+ @tasks = @tasks.where(status: order)
+ end
+ end
+
+ @member = @course.course_members.find_by(user_id: current_user.id, is_active: 1)
+ @all_count = @course.graduation_tasks.size
+ @published_count = @course.graduation_tasks.where("publish_time <= '#{Time.now}'").size
+ @task_count = @tasks.size
+ @tasks = @tasks.reorder("#{default_order}").page(page).per(15).includes(:graduation_works, course: [course_members: :teacher_course_groups])
+ end
+
+ # 任务问答
+ def show
+ @attachments = @task.attachments
+ @current_user = current_user
+ end
+
+ # 毕设任务列表
+ def tasks_list
+ # 搜索栏数据
+ @current_user = current_user
+
+ # 分页参数
+ page = params[:page] || 1
+ limit = params[:limit] || 20
+ @work = @task.graduation_works.where(user_id: current_user.id)
+ #end_time @task.allow_late ? @task.late_time : @task.end_time
+ # 任务发布的情况下: 是老师身份或者任务已截止的情况下公开任务了作品设置的学生也能查看其他人的作品
+ if @task.published? && (@user_course_identity < Course::STUDENT ||
+ (@user_course_identity == Course::STUDENT && @work.present? && @work.take.work_status > 0 &&
+ @task.status > 1 && @task.open_work))
+ # 如有有分班则看分班内的学生,否则看所有学生的作品
+ user_ids =
+ if @user_course_identity < Course::STUDENT
+ @course.teacher_group_user_ids(current_user.id)
+ else
+ course_group_id = @course.course_member(current_user.id).course_group_id
+ @course.course_members.where(course_group_id: course_group_id).pluck(:user_id)
+ end
+
+ @work_list = @task.graduation_works.where(user_id: user_ids).includes(user: [:user_extension])
+ @all_work_count = @work_list.count
+ @teachers = @course.teachers
+ # 教师评阅搜索 0: 未评, 1 已评
+ unless params[:teacher_comment].blank?
+ graduation_work_ids = GraduationWorkScore.where(graduation_work_id: @work_list.map(&:id)).pluck(:graduation_work_id)
+ if params[:teacher_comment] == 0
+ @work_list = @work_list.where.not(id: graduation_work_ids)
+ elsif params[:teacher_comment] == 1
+ @work_list = @work_list.where(id: graduation_work_ids).where.not(work_status: 0)
+ end
+ end
+
+ # 作品状态 0: 未提交, 1 按时提交, 2 延迟提交
+ unless params[:task_status].blank?
+ @work_list = @work_list.where(work_status: params[:task_status])
+ end
+
+ # 分班情况
+ unless params[:course_group].blank?
+ group_user_ids = @course.course_members.where(course_group_id: params[:course_group]).pluck(:user_id)
+ # 有分组只可能是老师身份查看列表
+ @work_list = @work_list.where(user_id: group_user_ids)
+ end
+
+ # 只看我的交叉评阅
+ unless params[:cross_comment].blank?
+ graduation_work_id = @task.graduation_work_comment_assignations.where(:user_id =>current_user.id)
+ .pluck(:graduation_work_id).uniq if @task.graduation_work_comment_assignations
+ @work_list = @work_list.where(id: graduation_work_id)
+ end
+
+ # 输入姓名和学号搜索
+ # TODO user_extension 如果修改 请调整
+ unless params[:search].blank?
+ @work_list = @work_list.joins(user: :user_extension).where("concat(lastname, firstname) like ?
+ or student_id like ?", "%#{params[:search]}%", "%#{params[:search]}%")
+ end
+
+ # 排序
+ rorder = params[:order] || "updated_at"
+ b_order = params[:b_order] || "desc"
+ if rorder == "created_at" || rorder == "work_score"
+ @work_list = @work_list.order("graduation_works.#{rorder} #{b_order}")
+ elsif rorder == "student_id"
+ @work_list = @work_list.joins(user: :user_extension).order("user_extensions.#{rorder} #{b_order}")
+ end
+
+
+ @work_count = @work_list.count
+ @work_excel = @work_list
+ @work_list = @work_list.page(page).limit(limit)
+ respond_to do |format|
+ format.json
+ format.xlsx{
+ if @user_course_identity >= Course::STUDENT
+ tip_exception(403, "无权限操作")
+ else
+ graduation_work_to_xlsx(@work_excel,@task,current_user)
+ exercise_export_name = current_user.real_name + "_" + @course.name + "_" + @task.name + "_" + Time.now.strftime('%Y%m%d_%H%M%S')
+ render xlsx: "#{exercise_export_name.strip.first(30)}",template: "graduation_tasks/tasks_list.xlsx.axlsx",locals: {table_columns:@head_cells_column, task_users:@task_cells_column}
+ end
+ }
+ format.zip{
+ if @user_course_identity >= Course::STUDENT
+ tip_exception(403, "无权限操作")
+ else
+ zip_works = @work_excel.where("work_status > 0")
+ status = checkfileSize(zip_works)
+ if status == 0
+ zipfile = zip_homework_common @task, zip_works
+ file = decode64(zipfile[0][:base64file])
+ send_file "#{OUTPUT_FOLDER}/#{file}", filename: filename_for_content_disposition(file), type: 'application/zip'
+ else
+ tip_exception(status == -1 ? "文件大小超过500M,请通过微信或者QQ联系管理员辅助您打包下载" : "无附件可下载")
+ end
+ end
+ }
+ end
+ else
+ @work_list = @work
+ @work_count = @work_list.count
+ @all_work_count = @work_list.count
+ respond_to do |format|
+ format.json
+ format.xlsx{
+ normal_status(-1,"作业未发布")
+ }
+ format.zip{
+ normal_status(-1,"作业未发布")
+ }
+ end
+ end
+ end
+
+
+ # 评论列表接口、 包含一级和二级评论的获取
+ def show_comment
+ @page = params[:page] || 1
+ @limit = params[:limit] || 10
+ @parent = params[:parent_id]
+ @current_user = current_user
+
+ @messages = @task.journals_for_messages
+ @messages_count = @messages.count
+ if @parent
+ @messages = @messages.where(m_parent_id: @parent).order("created_on asc")
+ else
+ @messages = @messages.parent_comment.order("created_on desc")
+ end
+
+ @messages = @messages.page(@page).per(@limit)
+ end
+
+ def create
+ ActiveRecord::Base.transaction do
+ begin
+ @graduation_task = GraduationTask.new(graduation_task_params)
+ @graduation_task.course_id = @course.id
+ @graduation_task.user_id = current_user.id
+ @graduation_task.base_on_project = @graduation_task.task_type == 2 ? 1 : 0
+ if @graduation_task.save!
+ # 为学生创建作品
+ @graduation_task.create_work_list
+
+ Attachment.associate_container(params[:attachment_ids], @graduation_task.id, @graduation_task.class) if params[:attachment_ids]
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def new
+ left_banner_content = @course.course_modules.search_by_module_type("graduation")
+ if left_banner_content.present?
+ banner = left_banner_content.first.course_second_categories.last
+ @left_banner_id = banner.id
+ @left_banner_name = banner.name
+ else
+ normal_status(-1,"左侧导航不存在!")
+ end
+ end
+
+ def edit
+ left_banner_content = @course.course_modules.search_by_module_type("graduation")
+ if left_banner_content.present?
+ banner = left_banner_content.first.course_second_categories.last
+ @left_banner_id = banner.id
+ @left_banner_name = banner.name
+ end
+ end
+
+ def update
+ ActiveRecord::Base.transaction do
+ begin
+ @task.update_attributes(graduation_task_params)
+ Attachment.associate_container(params[:attachment_ids], @task.id, @task.class) if params[:attachment_ids]
+ normal_status(0, "更新成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 设为公开
+ def set_public
+ tip_exception("仅公开课堂才能公开毕设任务") if @course.is_public == 0
+ tasks = @course.graduation_tasks.where(id: params[:task_ids])
+ tasks.update_all(is_public: 1)
+ normal_status(0, "更新成功")
+ end
+
+ # 删除多个任务
+ def multi_destroy
+ ActiveRecord::Base.transaction do
+ begin
+ tasks = @course.graduation_tasks.where(id: params[:task_ids])
+
+ tasks.destroy_all
+
+ # 这些写是因为model中的关联删除无法删除is_delete=0的作品
+ GraduationWork.where(graduation_work_id: tasks.pluck(:id)).destroy_all
+ normal_status(0, "删除成功")
+
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 加入题库
+ def add_to_bank
+ ActiveRecord::Base.transaction do
+ begin
+ tasks = @course.graduation_tasks.where(id: params[:task_ids])
+
+ tasks.each do |task|
+ task_bank = current_user.gtask_banks.find_by(graduation_task_id: task.id)
+
+ # 已加入的更新,未加入的新建
+ if task_bank.present?
+ task_bank.update_attributes(name: task.name, description: task.description, course_list_id: @course.course_list_id,
+ min_num: task.min_num, max_num: task.max_num, base_on_project: task.base_on_project)
+ task_bank.attachments.destroy_all
+ else
+ task_bank = GtaskBank.new(name: task.name, description: task.description, user_id: current_user.id,
+ task_type: task.task_type, quotes: 1, graduation_task_id: task.id,
+ min_num: task.min_num, max_num: task.max_num, base_on_project: task.base_on_project,
+ course_list_id: @course.course_list_id)
+
+ task_bank.save!
+ end
+ task.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = task_bank.user_id
+ att.copy_from = attachment.id
+ task_bank.attachments << att
+ end
+ end
+
+ normal_status(0,"加入题库成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def publish_task
+ tip_exception("缺少截止时间参数") if params[:end_time].blank?
+ tip_exception("截止时间必须晚于当前时间") if params[:end_time] <= strf_time(Time.now)
+
+ ActiveRecord::Base.transaction do
+ begin
+ tasks = @course.graduation_tasks.where(id: params[:task_ids], status: 0).
+ where("publish_time is null or publish_time > '#{Time.now}'")
+
+ tasks.each do |task|
+ task.publish_time = Time.now
+ task.status = 1
+
+ task.end_time = params[:end_time]
+
+ # 补交结束时间
+ task.late_time = Time.at(task.end_time.to_i + 30*24*3600) if task.allow_late && task.late_time.nil?
+
+ task.save!
+ GraduationTaskPublishNotifyJob.perform_later(task.id)
+ task.act_as_course_activity
+ end
+ normal_status(0, "发布成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def end_task
+ ActiveRecord::Base.transaction do
+ begin
+ tasks = @course.graduation_tasks.where(id: params[:task_ids], status: 1)
+
+ tasks.each do |task|
+ task.end_time = Time.now
+ task.status = task.allow_late ? 2 : 3
+ task.save!
+ end
+ normal_status(0, "更新成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def settings
+ @current_user = current_user
+ end
+
+ def update_settings
+ ActiveRecord::Base.transaction do
+ begin
+ unless @course.is_end
+
+ # 分组设置 已有提交作品或关联项目的,则“基于项目实施”不能修改, 已有提交作品的人数限制的范围只能扩大,不能缩小
+ if @task.task_type == 2
+ @task.min_num = @task.student_commit_works ? (params[:min_num].to_i > @task.min_num ? @task.min_num : params[:min_num])
+ : params[:min_num]
+ @task.max_num = @task.student_commit_works ? (params[:max_num].to_i < @task.max_num ? @task.max_num : params[:max_num])
+ : params[:max_num]
+ unless @task.student_relate_projects
+ tip_exception("base_on_project参数不能为空") if params[:base_on_project].blank?
+ @task.base_on_project = params[:base_on_project].to_i
+ end
+ end
+
+ # 发布设置
+ if @task.status == 0
+ tip_exception("发布时间不能为空") if params[:publish_time].blank?
+ tip_exception("截止时间不能为空") if params[:end_time].blank?
+ tip_exception("发布时间不能早于当前时间") if params[:publish_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S")
+ tip_exception("截止时间不能早于当前时间") if params[:end_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S")
+ tip_exception("截止时间不能早于发布时间") if params[:publish_time] > params[:end_time]
+ tip_exception("截止时间不能早于课堂结束时间") if @course.end_date.present? &&
+ params[:end_time] > @course.end_date.end_of_day
+
+ @task.publish_time = params[:publish_time]
+ @task.end_time = params[:end_time]
+ if @task.publish_time <= Time.now
+ @task.status = 1
+ send_tiding = true
+ end
+
+ elsif @task.status < 2
+ tip_exception("截止时间不能为空") if params[:end_time].blank?
+ tip_exception("截止时间不能早于当前时间") if params[:end_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S")
+ tip_exception("截止时间不能早于课堂结束时间") if @course.end_date.present? && params[:end_time] > @course.end_date.end_of_day
+ @task.end_time = params[:end_time]
+ end
+
+ # 补交设置
+ # @task.allow_late = params[:allow_late]
+ # @task.late_penalty = params[:allow_late].to_i == 1 ? params[:late_penalty] : 0
+ current_late_penalty = @task.late_penalty
+ if params[:allow_late].to_i == 1
+ tip_exception("补交结束时间不能为空") if params[:late_time].blank?
+ tip_exception("补交结束时间不能早于截止时间") if params[:late_time] <= @task.end_time
+ tip_exception("补交结束时间不能晚于课堂结束时间") if @course.end_date.present? && params[:late_time] >
+ @course.end_date.end_of_day
+ tip_exception("迟交扣分应为正整数") if params[:late_penalty] && params[:late_penalty].to_i < 0
+
+ @task.allow_late = true
+ @task.late_time = params[:late_time]
+ @task.late_penalty = params[:late_penalty].to_i
+ else
+ @task.allow_late = false
+ @task.late_penalty = 0
+ end
+
+ # 迟交扣分有变动则更新迟交学生的成绩
+ if @task.late_penalty != current_late_penalty
+ @task.graduation_works.where(work_status: 2).each do |work|
+ work.late_penalty = @task.late_penalty
+ work.save!
+ end
+ end
+
+ # 评分设置
+ unless @task.cross_comment && @task.comment_time && @task.comment_time < Time.now
+ @task.cross_comment = params[:cross_comment].to_i
+
+ tip_exception("评阅时间不能为空") if @task.cross_comment && params[:comment_time].blank?
+ tip_exception("评阅时间应当大于截止时间") if @task.cross_comment && params[:comment_time] <= @task.end_time
+
+ @task.comment_time = @task.cross_comment ? params[:comment_time] : nil
+ @task.comment_num = @task.cross_comment ? params[:comment_num].to_i : 3
+ @task.comment_status = @task.cross_comment ? params[:comment_status] : 0
+ if @task.cross_comment && params[:comment_status].to_i == 4
+ tip_exception("评阅数不能为空") if params[:comment_num].blank?
+ tip_exception("评阅数应大于0") if params[:comment_num].to_i < 1
+
+ @course.graduation_groups.each_with_index do |group, index|
+ ass_group = @task.graduation_task_group_assignations.find_by(graduation_group_id: group.id)
+ if ass_group.present? && params[:comment_group][index].present? && params[:comment_group][index] != "0"
+ ass_group.update_attributes(assign_graduation_group_id: params[:comment_group][index])
+ else
+ @task.graduation_task_group_assignations << GraduationTaskGroupAssignation.new(graduation_group_id: group.id,
+ assign_graduation_group_id: params[:comment_group][index])
+ end
+ end
+ end
+ end
+
+ # 公开设置
+ @task.open_work = params[:open_work] ? params[:open_work].to_i : 0
+ @task.open_score = params[:open_score] ? params[:open_score].to_i : 0
+ @task.save!
+
+ if send_tiding
+ GraduationTaskPublishNotifyJob.perform_later(@task.id)
+ @task.act_as_course_activity
+ end
+
+ normal_status(0, "更新成功")
+ else
+ tip_exception("课堂已结束不能再更新")
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ private
+ def find_task
+ begin
+ @task = GraduationTask.find(params[:id])
+ @course = @task.course
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("id不存在")
+ end
+ end
+
+ # 未发布时非老师角色不能访问,发布后非课堂成员不能访问未公开的任务
+ def task_publish
+ if (@user_course_identity >= Course::STUDENT && @task.status < 1) ||
+ (@user_course_identity > Course::STUDENT && (@course.is_public == 0 || !@task.is_public))
+ tip_exception(-1,"任务还未发布,无法查看")
+ end
+ end
+
+ def graduation_task_params
+ tip_exception("task_type参数不能为空") if params[:task_type].blank?
+ tip_exception("name参数不能为空") if params[:name].blank?
+ tip_exception("name参数不能超过60个字符") if params[:name].length > 60 #6.11 -hs
+ tip_exception("description参数不能为空") if params[:description].blank?
+ params.require(:graduation_task).permit(:task_type, :name, :description)
+ end
+
+ def require_id_params
+ tip_exception("请至少选择一个毕设任务") if params[:task_ids].blank?
+ tip_exception("批量设置不能超过15个") if params[:task_ids].length > 15
+ end
+
+ def valid_params
+ if @task.task_type == 2
+ tip_exception("最小人数不能为空") if params[:min_num].blank?
+ tip_exception("最大人数不能为空") if params[:max_num].blank?
+ tip_exception("最小人数不能少于1") if params[:min_num].to_i <= 0
+ tip_exception("最大人数不能小于最小人数要求") if params[:min_num].to_i > params[:max_num].to_i
+ end
+ end
+ #
+ # def graduation_work_to_xls items
+ # xls_report = StringIO.new
+ # book = Spreadsheet::Workbook.new
+ # sheet1 = book.create_worksheet :name => "学生成绩"
+ # blue = Spreadsheet::Format.new :color => :blue, :weight => :bold, :size => 10
+ # sheet1.row(0).default_format = blue
+ # course = @task.course
+ # count_row = 1
+ # list = 0
+ # if @task.task_type == 1
+ # if @task.cross_comment
+ # sheet1.row(0).concat(["学生id","真实姓名", "登录名", "学号", "电子邮箱", "分班", "作品描述", "教师评分","交叉评分", "迟交扣分",
+ # "成绩", "更新时间"])
+ # else
+ # sheet1.row(0).concat(["学生id", "真实姓名", "登录名", "学号", "电子邮箱", "分班", "作品描述", "教师评分", "迟交扣分",
+ # "成绩", "更新时间"])
+ # end
+ # items.each do |work|
+ # sheet1[count_row,list] = work.user.id
+ # sheet1[count_row,list+=1] = work.user.full_name
+ # sheet1[count_row,list+=1] = work.user.login
+ # sheet1[count_row,list+=1] = work.user.student_id
+ # sheet1[count_row,list+=1] = work.user.mail
+ # sheet1[count_row,list+=1] = work.class_grouping_name
+ # sheet1[count_row,list+=1] = strip_html work.description if work.description
+ # sheet1[count_row,list+=1] = work.teacher_score.nil? ? "未评分" : work.teacher_score.round(1)
+ # if @task.cross_comment
+ # sheet1[count_row,list+=1] = work.cross_score.nil? ? "未评分" : work.cross_score.round(1)
+ # end
+ # sheet1[count_row,list+=1] = work.late_penalty
+ # sheet1[count_row,list+=1] = work.respond_to?("work_score") ? work.work_score.nil? ? "未评分" : work.work_score.round(1) : "未评分"
+ # sheet1[count_row,list+=1] = format_time(work.update_time)
+ # count_row += 1
+ # list = 0
+ # end
+ # elsif @task.task_type == 2
+ # if @task.cross_comment
+ # sheet1.row(0).concat(["分组", "组员","分班", "作品描述", "教师评分","交叉评分","迟交扣分", "成绩", "更新时间"])
+ # else
+ # sheet1.row(0).concat(["分组", "组员","分班", "作品描述", "教师评分", "迟交扣分", "成绩", "更新时间"])
+ # end
+ # items.each do |work|
+ # sheet1[count_row,list] = work.group_id
+ # sheet1[count_row,list+=1] = work.user.full_name
+ # sheet1[count_row,list+=1] = work.class_grouping_name
+ # sheet1[count_row,list+=1] = strip_html work.description if work.description
+ # sheet1[count_row,list+=1] = work.teacher_score.nil? ? "未评分" : work.teacher_score.round(1)
+ # if @task.cross_comment
+ # sheet1[count_row,list+=1] = work.cross_score.nil? ? "未评分" : work.cross_score.round(1)
+ # end
+ # sheet1[count_row,list+=1] = work.late_penalty
+ # sheet1[count_row,list+=1] = work.respond_to?("work_score") ? work.work_score.nil? ? "未评分" : work.work_score.round(1) : "未评分"
+ # sheet1[count_row,list+=1] = format_time(work.update_time)
+ # count_row += 1
+ # list = 0
+ # end
+ # end
+ # book.write xls_report
+ # xls_report.string
+ # end
+
+end
diff --git a/app/controllers/graduation_topics_controller.rb b/app/controllers/graduation_topics_controller.rb
new file mode 100644
index 000000000..6bc3a4c2a
--- /dev/null
+++ b/app/controllers/graduation_topics_controller.rb
@@ -0,0 +1,312 @@
+class GraduationTopicsController < ApplicationController
+ before_action :require_login, except: [:index]
+ before_action :find_course
+ before_action :teacher_allowed, only: [:new, :create, :update, :edit, :destroys, :set_public,
+ :refuse_student_topic, :accept_student_topic, :export]
+ before_action :find_graduation_topic, except: [:index, :create, :new, :set_public, :destroys, :export]
+ before_action :find_course_teachers, only: [:new, :edit]
+ before_action :user_course_identity, only: [:index, :show, :show_detail, :show_comment]
+
+ include ExportHelper
+
+
+ # 毕设选题列表
+ def index
+ @graduation_topic = @course.graduation_topics
+ @current_user = current_user
+ # 搜索
+ if params[:search]
+ @graduation_topic = @graduation_topic.search_by_name(params[:search])
+ end
+
+ if params[:status]
+ @graduation_topic = @graduation_topic.search_by_status(params[:status])
+ end
+
+ # 当前用户是否已经选过题
+ # @user_selected = StudentGraduationTopic.where(graduation_topic_id: @graduation_topic, user_id: current_user.id).count > 0
+ user_graduation_topics = @course.student_graduation_topics.where(user_id: current_user.id, status: [0, 1], graduation_topic_id: @graduation_topic.pluck(:id)) #6.12 -hs
+ @user_selected = user_graduation_topics.size > 0
+ ## 分页参数
+ page = params[:page] || 1
+ limit = params[:limit] || 15
+ @graduation_topic_count = @graduation_topic.count
+ @graduation_topic = @graduation_topic.order("created_at desc").page(page).per(limit)
+ end
+
+ # 课题列表
+ def show
+ @student_graduation_topics =
+ if @user_course_identity > Course::STUDENT && !@graduation_topic.is_public
+ StudentGraduationTopic.none
+ else
+ @graduation_topic.student_graduation_topics.includes(:user, :course_member => [:course_group])
+ .order("student_graduation_topics.created_at desc")
+ end
+ ## 分页参数
+ @current_user = current_user
+ course_group_ids = @course.course_members.where(user_id: current_user.id, role: [1,2,3]).pluck(:course_group_id).uniq
+ #6.11 -hs
+ if course_group_ids.present?
+ if course_group_ids.include?(0)
+ course_group_ids = @course.course_groups.pluck(:id)
+ end
+ end
+
+ @group_list = CourseGroup.where(id: course_group_ids)
+
+ page = params[:page] || 1
+ limit = params[:limit] || 50
+ @users_count = @student_graduation_topics.try(:count).to_i
+ @student_graduation_topics = @student_graduation_topics.page(page).per(limit)
+ end
+
+ # 课题详情
+ def show_detail
+ @attachments = @graduation_topic.attachments
+ end
+
+ # 课题详情回复接口
+ def show_comment
+ @page = params[:page] || 1
+ @limit = params[:limit] || 10
+ @parent = params[:parent_id]
+ @current_user = current_user
+ @messages = @graduation_topic.journals_for_messages
+ if @parent
+ @messages = @messages.where(m_parent_id: @parent).order("created_on desc")
+ else
+ @messages = @messages.parent_comment.order("created_on asc")
+ end
+
+ @messages_count = @messages.count
+
+ @messages = @messages.page(@page).per(@limit)
+ end
+
+
+ # 新建课题
+ def new
+ #6.11 新增,-hs
+ left_banner_content = @course.course_modules.search_by_module_type("graduation")
+ if left_banner_content.present?
+ @left_banner_id = left_banner_content.first.course_second_categories.first.id
+ @left_banner_name = left_banner_content.first.course_second_categories.first.name
+ else
+ normal_status(-1,"左侧导航不存在!")
+ end
+ end
+
+ # 创建课题
+ # POST: /graduation_topics
+ def create
+ @graduation_topic = GraduationTopic.new(graduation_topic_params)
+ @graduation_topic.course_id = @course.id
+ @graduation_topic.user_id = current_user.id
+ @graduation_topic.save!
+ Attachment.associate_container(params[:attachment_ids], @graduation_topic.id, @graduation_topic.class) if params[:attachment_ids]
+
+ end
+
+ # 更新课程
+ def update
+ @graduation_topic.update_attributes(graduation_topic_params)
+ Attachment.associate_container(params[:attachment_ids], @graduation_topic.id, @graduation_topic.class) if params[:attachment_ids]
+ end
+
+ def edit
+ @attachments = @graduation_topic.attachments
+ left_banner_content = @course.course_modules.search_by_module_type("graduation")
+ if left_banner_content.present?
+ @left_banner_id = left_banner_content.first.course_second_categories.first.id
+ @left_banner_name = left_banner_content.first.course_second_categories.first.name
+ end
+ end
+
+ def destroys
+ tip_exception(-1, "graduation_topic_ids不能为空") if params[:graduation_topic_ids].nil?
+ @graduation_topics = GraduationTopic.where(:id => params[:graduation_topic_ids])
+ @graduation_topics.destroy_all
+ normal_status("删除成功")
+ end
+
+ def set_public
+ tip_exception(-1, "私有课程不能设置毕设选题为公开") if @course.is_public == 0
+ tip_exception(-1, "graduation_topic_ids不能为空") if params[:graduation_topic_ids].nil?
+ @graduation_topics = GraduationTopic.where(id: params[:graduation_topic_ids])
+ @graduation_topics.update_all(is_public: true)
+ normal_status("设置成功")
+ end
+
+ # 拒绝课题
+ def refuse_student_topic
+ begin
+ student_graduation_topic = @graduation_topic.student_graduation_topics
+ .find_by(id: params[:student_graduation_topic])
+ if student_graduation_topic.present?
+ # 更新学生选题和老师选题的状态
+ student_graduation_topic.update_attributes(:status => 2)
+ update_graduation_topic_status
+
+ # 拒绝后将该学生移动到未分班中
+ student_member = @course.course_members.where(:user_id => student_graduation_topic.user_id).first
+ student_member.update_attributes(:course_group_id => 0) if student_member.present?
+
+ student_graduation_topic.tidings.update_all(:status => 1)
+ Tiding.create(:user_id => student_graduation_topic.user_id, :trigger_user_id => current_user.id,
+ :container_id => student_graduation_topic.id, :container_type => "DealStudentTopicSelect",
+ :parent_container_id => @graduation_topic.id, :parent_container_type => "GraduationTopic",
+ :belong_container_id => @graduation_topic.course_id, :belong_container_type => "Course",
+ :viewed => 0, :tiding_type => "GraduationTopic", :status => 2)
+ end
+ normal_status("拒绝成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(-1, "操作失败")
+ end
+ end
+
+ # 接受课题
+ def accept_student_topic
+ student_graduation_topic = @graduation_topic.student_graduation_topics
+ .where(:id => params[:student_graduation_topic_id]).first
+ if student_graduation_topic.present?
+ # 更新学生选题和老师选题的状态
+ student_graduation_topic.update_attributes(:status => 1)
+ update_graduation_topic_status
+
+ # 将学生加入到老师的第一个具有管理权限的分班(没有则创建一个“XXX老师组”分班并弹框提醒)
+ teacher_group = @course.teacher_course_groups.where(:user_id => @graduation_topic.tea_id, :id => params[:group_id]).first
+ unless teacher_group.present?
+ member = @course.course_members.where(:user_id => @graduation_topic.tea_id).first
+ tip_exception("分班名称不能为空") if params[:course_group_name].blank?
+ course_group = CourseGroup.create(:name => params[:course_group_name], :course_id => @course.id)
+ teacher_group = TeacherCourseGroup.create(:course_id => @course.id, :member_id => member.try(:id),
+ :user_id => @graduation_topic.tea_id,
+ :course_group_id => course_group.try(:id))
+ end
+ student_member = @course.course_members.where(:user_id => student_graduation_topic.user_id).first
+ student_member.update_attributes(:course_group_id => teacher_group.course_group_id) if student_member.present?
+
+ student_graduation_topic.tidings.update_all(:status => 1)
+ Tiding.create(:user_id => student_graduation_topic.user_id, :trigger_user_id => current_user.id,
+ :container_id => student_graduation_topic.id, :container_type => "DealStudentTopicSelect",
+ :parent_container_id => @graduation_topic.id, :parent_container_type => "GraduationTopic",
+ :belong_container_id => @graduation_topic.course_id, :belong_container_type => "Course",
+ :viewed => 0, :tiding_type => "GraduationTopic", :status => 1)
+ end
+ normal_status("同意成功")
+ end
+
+ # 学生选题
+ def student_select_topic
+ user_unaccept_topics = @course.student_graduation_topics.where(user_id: current_user.id, status: [0, 1])
+ if user_unaccept_topics.size == 0
+ member_id = @course.course_members.find_by_user_id(current_user.id)
+ StudentGraduationTopic.create(course_id: @course.id, user_id: current_user.id, member_id: member_id,
+ graduation_topic_id: @graduation_topic.id)
+ @graduation_topic.update_attribute(:status, 1)
+ normal_status("选题成功")
+ else
+ normal_status("已经选过题,无法重复选题")
+ end
+ end
+
+ # 学生取消选题
+ def student_cancel_topic
+ user_unaccept_topics = @course.student_graduation_topics.where(user_id: current_user.id, status: [0, 1])
+ user_unaccept_topics.destroy_all
+ update_graduation_topic_status
+ normal_status(0,"取消成功")
+ end
+
+ # 加入题库
+ def add_to_bank
+ ActiveRecord::Base.transaction do
+ begin
+ topics = @course.graduation_topics.where(id: params[:topic_ids])
+
+ topics.each do |topic|
+ topic_bank = current_user.gtopic_banks.find_by(graduation_topic_id: topic.id)
+
+ # 已加入的更新,未加入的新建
+ if topic_bank.present?
+ topic_bank.update_attributes(name: topic, description: topic.description,
+ topic_source: topic.topic_source,
+ topic_property_first: topic.topic_property_first,
+ topic_property_second: topic.topic_property_second,
+ source_unit: topic.source_unit,
+ topic_repeat: topic.topic_repeat,
+ province: topic.province,
+ city: topic.city,
+ course_list_id: @course.course_list_id)
+ topic_bank.attachments.destroy_all
+ else
+ topic_bank = GtopicBank.new(name: topic, description: topic.description,
+ topic_source: topic.topic_source,
+ topic_property_first: topic.topic_property_first,
+ topic_property_second: topic.topic_property_second,
+ source_unit: topic.source_unit,
+ topic_repeat: topic.topic_repeat,
+ province: topic.province,
+ city: topic.city,
+ course_list_id: @course.course_list_id,
+ user_id: current_user.id,
+ graduation_topic_id: topic.id)
+
+ topic_bank.save!
+ end
+ topic.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = topic_bank.user_id
+ att.copy_from = attachment.id
+ topic_bank.attachments << att
+ end
+ end
+
+ normal_status(0,"加入题库成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 导出功能
+ def export
+ course = @course
+ students = course.students.joins(user: :user_extension).order("user_extensions.student_id")
+ graduation_topic_to_xlsx(students,course)
+ exercise_export_name = current_user.real_name + "_" + course.name + "_毕设选题" + "_" + Time.now.strftime('%Y%m%d_%H%M%S')
+ render xlsx: "#{exercise_export_name.strip.first(30)}",template: "graduation_topics/export.xlsx.axlsx",locals: {table_columns:@topic_head_cells,topic_users:@topic_body_cells}
+ end
+
+ private
+ def find_graduation_topic
+ begin
+ @graduation_topic = GraduationTopic.find params[:id]
+ rescue Exception => e
+ uid_logger(e.message)
+ missing_template
+ end
+ end
+
+ def find_course_teachers
+ @teachers = @course.teachers.joins(:user).order("users.id = #{current_user.id} desc,
+ CONVERT(users.lastname USING gbk) COLLATE gbk_chinese_ci asc")
+ end
+
+ # 创建允许参数
+ def graduation_topic_params
+ params.require(:graduation_topic).permit(:tea_id, :name, :topic_type, :topic_source, :topic_property_first,
+ :description, :topic_property_second, :status, :source_unit, :topic_repeat,
+ :province, :city, :is_public)
+ end
+
+ # 更新选题的状态
+ def update_graduation_topic_status
+ status = @graduation_topic.student_graduation_topic_status
+ @graduation_topic.update_attribute(:status, status)
+ end
+end
diff --git a/app/controllers/graduation_works_controller.rb b/app/controllers/graduation_works_controller.rb
new file mode 100644
index 000000000..9117112d3
--- /dev/null
+++ b/app/controllers/graduation_works_controller.rb
@@ -0,0 +1,534 @@
+class GraduationWorksController < ApplicationController
+ before_action :require_login
+ before_action :find_task, only: [:new, :create, :search_member_list, :check_project, :relate_project,
+ :cancel_relate_project]
+ before_action :find_work, only: [:show, :edit, :update, :revise_attachment, :supply_attachments, :comment_list,
+ :add_score, :delete_score, :adjust_score, :assign_teacher]
+ before_action :user_course_identity
+ before_action :task_public
+ before_action :teacher_allowed, only: [:add_score, :adjust_score, :assign_teacher]
+ before_action :course_student, only: [:new, :create, :edit, :update, :search_member_list, :relate_project,
+ :cancel_relate_project]
+ before_action :my_work, only: [:edit, :update, :revise_attachment]
+ before_action :published_task, only: [:new, :create, :edit, :update, :search_member_list, :relate_project,
+ :cancel_relate_project, :revise_attachment]
+ before_action :edit_duration, only: [:edit, :update]
+
+ def new
+ if @task.task_type == 2 && @task.base_on_project
+ work = @task.graduation_works.where(user_id: current_user.id).first
+ if work.present? && (work.work_status != 0 || work.project_id == 0)
+ normal_status(403, "")
+ end
+ end
+ @user = current_user
+ end
+
+ # 搜索课堂学生
+ def search_member_list
+ unless params[:search].blank?
+ # 有搜索条件时搜索课堂所有学生包括已提交的
+ users = User.joins(:graduation_works).where("concat(users.lastname, users.firstname) like ? and
+ graduation_task_id = #{@task.id}", "%#{params[:search]}%")
+ user_ids = users.pluck(:id) - [current_user.id]
+ @members = @course.students.where(user_id: user_ids)
+ else
+
+ # 没有搜索条件时搜索课堂所有未提交的学生
+ user_ids = @task.graduation_works.where("work_status = 0").pluck(:user_id) - [current_user.id]
+ @members = @course.students.where(user_id: user_ids)
+ end
+
+ page = params[:page] ? params[:page].to_i : 1
+ limit = params[:limit] ? params[:limit].to_i : 10
+
+ # todo user_extension
+ @members = @members.page(page).per(limit).includes(:course_group, user: :user_extension)
+ end
+
+ # 判断项目是否已有其他作品关联上了
+ def check_project
+ tip_exception("项目id不能为空") if params[:project_id].blank?
+ work = @task.graduation_works.where(project_id: params[:project_id]).first
+ @is_relate = work.present?
+ @relate_user = work.present? ? work.user.real_name : ""
+ end
+
+ def relate_project
+ tip_exception("项目id不能为空") if params[:project_id].blank?
+
+ ActiveRecord::Base.transaction do
+ begin
+ # 判断项目是否存在且当前用户是项目管理员
+ project = Project.where(id: params[:project_id]).first
+ member = Member.where(project_id: project.try(:id), user_id: current_user.id).first
+
+ if project.present? && member.present? && member.member_roles.first.try(:role_id) == 3
+
+
+ work = @task.graduation_works.where("user_id = #{current_user.id}").first ||
+ GraduationWork.create(user_id: current_user.id, graduation_task_id: @task.id, course_id: @task.course_id)
+
+ if work.work_status == 0 && work.project_id == 0
+ work.update_attributes(project_id: project.id, update_time: Time.now)
+
+ # 将老师加入项目
+ project_member = project.members.where(user_id: @task.user_id).first
+ if project_member.present?
+ project_member.member_roles.first.update_attributes(role_id: 3) if project_member.member_roles.first.present?
+ else
+ member = Member.create(user_id: @task.user_id, project_id: project.id)
+ member.member_roles << MemberRole.new(role_id: 3)
+ Tiding.create(user_id: @task.user_id, trigger_user_id: current_user.id, container_id: project.id,
+ container_type: 'ManagerJoinProject', belong_container_id: project.id,
+ belong_container_type: "Project", tiding_type: "System", extra: 3)
+ end
+ normal_status("关联成功")
+ else
+ tip_exception("不能重复关联项目")
+ end
+ else
+ tip_exception("该项目不存在")
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def cancel_relate_project
+ work = @task.graduation_works.where(user_id: current_user.id, work_status: 0).first
+ if work.present? && work.project.present?
+ ActiveRecord::Base.transaction do
+ begin
+ member = work.project.members.where(user_id: @task.user_id).first
+ member.destroy if member.present?
+ Tiding.where(user_id: @task.user_id, trigger_user_id: current_user.id, container_id: work.project.id,
+ container_type: 'ManagerJoinProject').destroy_all
+
+ work.update_attributes(project_id: 0)
+ normal_status("取消关联成功")
+
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ else
+ tip_exception("无法取消关联")
+ end
+ end
+
+ def create
+ graduation_work = @task.graduation_works.where(user_id: current_user.id).first ||
+ GraduationWork.create(user_id: current_user.id, graduation_task_id: @task.id, course_id: @task.course_id)
+
+ update_check graduation_work
+
+ tip_exception("作业不可重复提交") if graduation_work.work_status != 0
+ tip_exception("已过了提交时间") if @course.is_end || (@task.end_time < Time.now && (!@task.allow_late ||
+ (@task.late_time.present? && @task.late_time < Time.now)))
+
+ student_ids = [current_user.id]
+ ActiveRecord::Base.transaction do
+ begin
+ # work.update_attributes(graduation_work_params)
+
+ graduation_work.description = params[:description]
+ graduation_work.commit_time = Time.now
+ graduation_work.update_time = Time.now
+ graduation_work.commit_user_id = current_user.id
+ graduation_work.course_id = @course.id
+ graduation_work.group_id = @task.task_type == 2 ? @task.graduation_works.where("work_status != 0").map(&:group_id).max.to_i + 1 : 0
+
+ #提交作品时,计算是否迟交
+ graduation_work.late_penalty = @task.end_time < Time.now.to_s ? @task.late_penalty : 0
+ graduation_work.work_status = @task.end_time < Time.now.to_s ? 2 : 1
+
+ if graduation_work.save!
+ Attachment.associate_container(params[:attachment_ids], graduation_work.id, graduation_work.class)
+
+ if @task.task_type == 2
+ members = (params[:user_ids] || []).collect(&:to_i) - [current_user.id]
+ members = @course.students.pluck(:user_id) & members
+ student_ids += members
+ for i in 0 .. members.count-1
+ stu_work = @task.graduation_works.where(user_id: members[i].to_i).first || GraduationWork.new
+ stu_work.update_attributes(user_id: members[i].to_i, description: graduation_work.description,
+ graduation_task_id: @task.id, project_id: graduation_work.project_id,
+ late_penalty: graduation_work.late_penalty, work_status: graduation_work.work_status,
+ commit_time: Time.now, update_time: Time.now, group_id: graduation_work.group_id,
+ commit_user_id: current_user.id)
+ stu_work.save!
+ graduation_work.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = attachment.author_id
+ stu_work.attachments << att
+ end
+ end
+ end
+ @task.update_column(:updated_at, Time.now)
+ # todo 更新对应的毕设任务课堂动态
+ # update_course_activity(@taskhomework.class,@task.id)
+ @work_id = graduation_work.id
+ end
+
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+
+ SubmitGraduationWorkNotifyJob.perform_later(@task.id, student_ids)
+ end
+ end
+
+ def edit
+ @task_user = current_user
+ if @task.task_type == 2
+ @work_members = @course.students.where(user_id: @task.graduation_works.where(group_id: @work.group_id).pluck(:user_id)).
+ order("course_members.id=#{@work.user_id} desc").includes(:course_group, user: :user_extension)
+ end
+ end
+
+ def update
+ update_check @work
+
+ student_ids = []
+ ActiveRecord::Base.transaction do
+ begin
+ @work.description = params[:description]
+ @work.update_time = Time.now
+ @work.commit_user_id = current_user.id
+ if @work.save!
+ Attachment.associate_container(params[:attachment_ids], @work.id, @work.class)
+
+ #如果学生作品被打分后修改,应该给老师提示
+ student_ids << @work.user_id if @work.scored?
+ if @task.task_type == 2
+ graduation_works = @task.graduation_works.where("group_id = #{@work.group_id} and user_id != #{@work.user_id}")
+ work_user_ids = graduation_works.pluck(:user_id)
+ params_user_ids = (params[:user_ids] || []).collect(&:to_i) - [@work.user_id]
+ params_user_ids = @course.students.pluck(:user_id) & params_user_ids
+
+ # 原成员更新描述、更新时间以及附件
+ @task.graduation_works.where(group_id: @work.group_id, user_id: (work_user_ids & params_user_ids)).each do |work|
+ work.update_attributes(update_time: Time.now, description: @work.description, commit_user_id: current_user.id)
+ work.attachments.destroy_all
+ @work.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = attachment.author_id
+ work.attachments << att
+ end
+ student_ids << work.user_id if work.scored?
+ end
+
+ # 删除的成员
+ delete_user_ids = work_user_ids - params_user_ids
+ @task.graduation_works.where(group_id: @work.group_id, user_id: delete_user_ids).each do |work|
+ work.attachments.destroy_all
+ # work.student_works_scores.destroy_all
+ work.tidings.destroy_all
+ end
+ @task.graduation_works.where(group_id: @work.group_id, user_id: delete_user_ids).
+ update_all(work_status: 0, description: nil, late_penalty: 0, commit_time: nil, update_time: nil,
+ final_score: nil, teacher_score: nil, work_score: nil, project_id: 0, group_id: 0,
+ commit_user_id: nil)
+
+ # 新增加的成员
+ (params_user_ids - work_user_ids).each do |user_id|
+ stu_work = @task.graduation_works.where(user_id: user_id).empty? ? GraduationWork.new :
+ @task.graduation_works.where(user_id: user_id).first
+ stu_work.update_attributes(user_id: user_id, description: @work.description, graduation_task_id: @task.id,
+ project_id: @work.project_id, late_penalty: @work.late_penalty,
+ work_status: @work.work_status, commit_time: Time.now, update_time: Time.now,
+ group_id: @work.group_id, commit_user_id: current_user.id)
+ @work.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = attachment.author_id
+ stu_work.attachments << att
+ end
+
+ student_ids << user_id
+ end
+ end
+
+ normal_status("更新成功")
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+
+ SubmitGraduationWorkNotifyJob.perform_later(@task.id, student_ids) if student_ids.present?
+ end
+ end
+
+ def show
+ @current_user = current_user
+ @is_author = @work.user_id == current_user.id
+ @work_members = @task.task_type == 1 ? [] : @task.graduation_works.where.not(user_id: @work.user_id).
+ where(group_id: @work.group_id).includes(:user)
+ @attachments = @work.attachments.where.not(attachtype: 7)
+ end
+
+ def comment_list
+ @current_user = current_user
+ @last_comment = @work.graduation_work_scores.where(user_id: @current_user.id).last
+ @comment_scores = @work.graduation_work_scores.reorder("created_at desc").includes(:user)
+ end
+
+ # 给作品评分
+ def add_score
+ tip_exception("该学生的分数已经过调整,不能再评阅") if @work.ultimate_score
+ tip_exception("分数和评语不能都为空") if params[:score].blank? && params[:comment].blank?
+ tip_exception("分数不能超过0-100") if params[:score] && (params[:score].to_f < 0 || params[:score].to_f > 100)
+
+ ActiveRecord::Base.transaction do
+ begin
+ # 没传score则取上次评分成绩
+ score = GraduationWorkScore.where(user_id: current_user.id, graduation_work_id: @work.id).last
+ new_score = GraduationWorkScore.new
+ new_score.score = params[:score].blank? ? score.try(:score) : params[:score].to_f
+ new_score.comment = params[:comment] if params[:comment] && params[:comment].strip != ""
+ new_score.user_id = current_user.id
+ new_score.graduation_work_id = @work.id
+ new_score.graduation_task_id = @task.id
+
+ # 如果作品是未提交的状态则更新为已提交
+ if !new_score.score.nil? && @work.work_status == 0
+ @work.update_attributes(work_status: 1, commit_time: Time.now)
+ if @task.task_type == 2
+ @work.update_attributes(group_id: @task.graduation_works.where("work_status != 0").select("distinct group_id").count + 1)
+ end
+ end
+
+ if @task.status == 3 && @task.graduation_work_comment_assignations.where(graduation_work_id: @work.id, user_id: current_user.id).count > 0
+ new_score.reviewer_role = 2
+ else
+ new_score.reviewer_role = 1
+ end
+
+ if new_score.save!
+ Attachment.associate_container(params[:attachment_ids], new_score.id, new_score.class)
+
+ # 该用户的历史评阅无效
+ score.update_column('is_invalid', true) if score.present? && score.score.present?
+
+ Tiding.create(user_id: @work.user_id, trigger_user_id: User.current.id, container_id: new_score.id,
+ container_type: "GraduationWorkScore", parent_container_id: @work.id,
+ parent_container_type: "GraduationWork", belong_container_id: @task.course_id,
+ belong_container_type: "Course", viewed: 0, tiding_type: "GraduationTask", extra: new_score.reviewer_role)
+
+ case new_score.reviewer_role
+ when 1 #教师评分取平均分
+ ts_score = GraduationWorkScore.find_by_sql("SELECT AVG(score) AS score FROM graduation_work_scores WHERE
+ graduation_work_id = #{@work.id} AND reviewer_role = 1 AND score IS NOT NULL AND is_invalid = 0")
+
+ @work.teacher_score = ts_score.first.score.nil? ? nil : ts_score.first.score.try(:round, 2).to_f
+
+ # 分组作业整组同评
+ if @task.task_type == 2 && params[:same_score]
+ add_graduation_score_to_member @work, @task, new_score
+ end
+
+ when 2 #交叉评分显示平均分
+ ts_score = GraduationWorkScore.find_by_sql("SELECT AVG(score) AS score FROM graduation_work_scores WHERE
+ graduation_work_id = #{@work.id} AND reviewer_role = 2 AND score IS NOT NULL AND is_invalid = 0")
+ @work.cross_score = ts_score.first.score.nil? ? nil : ts_score.first.score.try(:round, 2).to_f
+ end
+
+ @task.update_column('updated_at', Time.now)
+ # update_course_activity(@task.class, @task.id)
+ @work.save!
+
+ normal_status("提交成功")
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def adjust_score
+ tip_exception("分数不能为空") if params[:score].blank?
+ tip_exception("分数不能超过0-100") if params[:score].to_f < 0 || params[:score].to_f > 100
+ ActiveRecord::Base.transaction do
+ begin
+ # 分数不为空的历史评阅都置为失效
+ @work.graduation_work_scores.where.not(score: nil).update_all(is_invalid: 1)
+
+ new_score = GraduationWorkScore.new(graduation_work_id: @work.id, score: params[:score].to_f,
+ graduation_task_id: @task.id, comment: "使用调分功能调整了作业最终成绩:#{params[:comment]}",
+ user_id: User.current.id, reviewer_role: 1, is_ultimate: 1)
+ new_score.save!
+ @work.update_attributes(ultimate_score: 1, work_score: params[:score].to_f)
+
+ normal_status("调分成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 删除教师/教辅的评分记录
+ def delete_score
+ score = @work.graduation_work_scores.where(id: params[:comment_id]).first
+ if score.present? && (score.is_invalid || score.score.nil?) && (score.user == current_user || current_user.admin?)
+ begin
+ score.destroy
+ normal_status("删除成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ else
+ tip_exception("无法删除")
+ end
+ end
+
+ def supply_attachments
+ @revise_attachments = @work.attachments.where(attachtype: 7)
+ @last_atta = @revise_attachments.last
+ end
+
+ def revise_attachment
+ tip_exception("不在补交阶段内") if @course.is_end || @task.end_time > Time.now || !@task.allow_late ||
+ (@task.late_time && @task.late_time < Time.now)
+ tip_exception("附件参数有误") if params[:attachment_ids].blank? || !params[:attachment_ids].is_a?(Array)
+ tip_exception("补交附件原因不能为空") if params[:description].blank?
+
+ ActiveRecord::Base.transaction do
+ begin
+ revise_attachment = @work.attachments.where(attachtype: 7).reorder("created_on desc").last
+ if revise_attachment.present? && @work.graduation_work_scores.where("created_at > '#{revise_attachment.created_on}'
+ and score is not null").count == 0
+ revise_attachment.destroy
+ end
+ Attachment.associate_container(params[:attachment_ids], @work.id, @work.class, 7)
+ revise_attachment = Attachment.where(attachtype: 7, container_id: @work.id, container_type: "GraduationWork").last
+ revise_attachment.update_attributes(description: params[:description]) if revise_attachment.present?
+
+ @work.update_attributes(update_time: Time.now)
+
+ normal_status("提交成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 交叉评阅分配老师
+ def assign_teacher
+ tip_exception(-1, "user_id不能为空") if params[:user_id].nil?
+ @work_assign_teacher = @work.graduation_work_comment_assignations.first
+
+ if @work_assign_teacher
+ # graduation_group_id: 已经是答辩组的需要 将答辩组清空
+ @work_assign_teacher.update_attributes(user_id: params[:user_id], graduation_group_id: 0)
+ else
+ @work.graduation_work_comment_assignations << GraduationWorkCommentAssignation.new(graduation_task_id: @task.id,
+ user_id: params[:user_id],
+ graduation_group_id: 0)
+ end
+ normal_status("分配成功")
+ end
+
+ private
+ def find_task
+ begin
+ @task = GraduationTask.find(params[:graduation_task_id])
+ @course = @task.course
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("id不存在")
+ end
+ end
+
+ def find_work
+ begin
+ @work = GraduationWork.find(params[:id])
+ @task = @work.graduation_task
+ @course = @task.course
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("id不存在")
+ end
+ end
+
+ def task_public
+ tip_exception(403,"没有操作权限") if @user_course_identity > Course::STUDENT && (@course.is_public == 0 ||
+ (@course.is_public == 1 && !@task.is_public))
+ end
+
+ def course_student
+ tip_exception(403,"没有操作权限") if @user_course_identity != Course::STUDENT
+ end
+
+ def my_work
+ tip_exception(403,"没有操作权限") if @work.user_id != current_user.id || @work.work_status == 0
+ end
+
+ def published_task
+ tip_exception("不能在非提交时间内操作") if @task.status == 0 || (!@task.allow_late && @task.status > 1) ||
+ (@task.allow_late && @task.late_time && @task.late_time < Time.now)
+ end
+
+ def edit_duration
+ tip_exception("已过了修改时间") if @task.end_time && @task.end_time < Time.now
+ end
+
+ def update_check work
+ tip_exception("作品描述不能为空") if params[:description].blank?
+ if @task.task_type == 2
+ tip_exception("小组成员不能为空") if params[:user_ids].blank?
+ tip_exception("小组成员人数不合要求") if params[:user_ids].length > @task.max_num || params[:user_ids].length < @task.min_num
+ tip_exception("请先关联项目") if @task.base_on_project && work.project_id == 0
+ end
+ end
+
+ def graduation_work_params
+ params.require(:graduation_work).permit(:description)
+ end
+
+ def add_graduation_score_to_member work, task, new_score
+ graduation_works = task.graduation_works.where("group_id = #{work.group_id} and id != #{work.id} and ultimate_score = 0")
+ graduation_works.each do |st_work|
+ st_score = GraduationWorkScore.new(user_id: new_score.user_id, score: new_score.score,
+ reviewer_role: new_score.reviewer_role, comment: new_score.comment)
+ st_work.graduation_work_scores << st_score
+
+ score = GraduationWorkScore.where(user_id: new_score.user_id, graduation_work_id: st_work.id).last
+ # 该用户的历史评阅无效
+ score.update_column('is_invalid', true) if score.present? && score.score.present?
+
+ teacher_score = GraduationWorkScore.find_by_sql("SELECT AVG(score) AS score FROM graduation_work_scores WHERE
+ graduation_work_id = #{work.id} AND reviewer_role = 1 AND score IS NOT NULL AND is_invalid = 0")
+
+ st_work.teacher_score = teacher_score.first.score.nil? ? nil : teacher_score.first.score.try(:round, 2).to_f
+ st_work.save!
+
+ Tiding.create(user_id: st_work.user_id, trigger_user_id: User.current.id, container_id: st_score.id,
+ container_type: "GraduationWorkScore", parent_container_id: st_work.id,
+ parent_container_type: "GraduationWork", belong_container_id: task.course_id,
+ belong_container_type: "Course", viewed: 0, tiding_type: "GraduationTask", extra: st_score.reviewer_role)
+ # 评阅附件的复制
+ new_score.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = st_score.user_id
+ st_score.attachments << att
+ end
+ end
+ end
+end
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
new file mode 100644
index 000000000..37cfed3fb
--- /dev/null
+++ b/app/controllers/home_controller.rb
@@ -0,0 +1,40 @@
+class HomeController < ApplicationController
+
+ def index
+ # banner图
+ images = PortalImage.where(status: true).order("position asc")
+ @images_url = []
+ images.each do |image|
+ @images_url << {path: image.link, image_url: "/images/avatars/PortalImage/#{image.id}"}
+ end
+
+ # 目录分级
+ repertoires = Repertoire.includes(sub_repertoires: :tag_repertoires).order("updated_at asc")
+ @rep_list = []
+ repertoires.each do |rep|
+
+ sub_rep_list = []
+ rep.sub_repertoires.each do |sub_rep|
+ tag_rep_list = []
+ sub_rep.tag_repertoires.each do |tag_rep|
+ tag_rep_list << {tag_id: tag_rep.id, tag_name: tag_rep.name}
+ end
+ sub_rep_list << {sub_rep_id: sub_rep.id, sub_rep_name: sub_rep.name, tag_rep_list: tag_rep_list}
+ end
+ @rep_list << {rep_id: rep.id, rep_name: rep.name, sub_rep_list: sub_rep_list}
+ end
+
+ @shixuns = Shixun.where(homepage_show: 1).includes(:tag_repertoires, :challenges).limit(8)
+
+ @subjects = Subject.where(homepage_show: 1).includes(:shixuns, :repertoire).limit(8)
+
+ @tea_users = User.where(homepage_teacher: 1).includes(:user_extension).limit(10).order("experience desc")
+ @stu_users = User.includes(:user_extension).where(user_extensions: {identity: 1}).limit(10).order("experience desc")
+ end
+
+ def search
+ @fuzzy_searchs = params[:keyword].split(" ").join("%")
+ @shixuns = Shixun.where("name like ?", "%#{@fuzzy_searchs}%")
+ @total_count = @shixuns.count
+ end
+end
diff --git a/app/controllers/homework_commons_controller.rb b/app/controllers/homework_commons_controller.rb
new file mode 100644
index 000000000..d3ece9feb
--- /dev/null
+++ b/app/controllers/homework_commons_controller.rb
@@ -0,0 +1,1461 @@
+class HomeworkCommonsController < ApplicationController
+ include HomeworkCommonsHelper
+ include ApplicationHelper
+ include ExportHelper
+
+ before_action :require_login, except: [:index, :choose_category]
+
+ before_action :find_course, only: [:index, :create, :new, :shixuns, :subjects, :create_shixun_homework, :publish_homework,
+ :end_homework, :set_public, :choose_category, :move_to_category, :choose_category,
+ :create_subject_homework, :multi_destroy, :add_to_homework_bank]
+ before_action :find_homework, only: [:edit, :show, :update, :group_list, :homework_code_repeat, :code_review_results,
+ :code_review_detail, :show_comment, :settings, :works_list, :update_settings,
+ :reference_answer, :publish_groups, :end_groups, :alter_name, :update_explanation]
+ before_action :user_course_identity
+ before_action :homework_publish, only: [:show, :works_list, :code_review_results, :show_comment, :settings, :reference_answer]
+ before_action :teacher_allowed, only: [:new, :edit, :create, :update, :shixuns, :subjects, :create_shixun_homework,
+ :publish_homework, :end_homework, :set_public, :choose_category, :move_to_category,
+ :choose_category, :create_subject_homework, :multi_destroy, :group_list, :homework_code_repeat,
+ :code_review_results, :code_review_detail, :update_explanation, :update_settings,
+ :add_to_homework_bank, :publish_groups, :end_groups]
+ before_action :require_id_params, only: [:set_public, :multi_destroy, :publish_homework, :end_homework, :move_to_category,
+ :add_to_homework_bank]
+ before_action :course_manager, only: [:alter_name]
+
+ def index
+ tip_exception("type参数有误") if params[:type] && ![1, 3, 4].include?(params[:type].to_i)
+ @user = current_user
+ search = "#{params[:search].to_s.strip.downcase}" if params[:search]
+ order = params[:order]
+ page = params[:page] ? params[:page].to_i : 1
+ @homework_type = params[:type] ? params[:type].to_i : 4
+ module_type = @homework_type == 4 ? "shixun_homework" : @homework_type == 1 ? "common_homework" : "group_homework"
+ @homework_commons = @course.homework_commons.where(homework_type: @homework_type)
+ @main_category = @course.course_modules.find_by(module_type: module_type)
+ if @homework_type == 4 && !params[:category].blank?
+ @category = @main_category.course_second_categories.find_by(id: params[:category])
+ tip_exception("子目录id有误") if !@category.present?
+ @homework_commons = @homework_commons.where(course_second_category_id: params[:category])
+ elsif @homework_type == 4
+ @homework_commons = @homework_commons.where(course_second_category_id: 0)
+ end
+ @all_count = @homework_commons.size
+
+ @member = @course.course_members.find_by(user_id: current_user.id, is_active: 1)
+ # 老师显示所有的作业、未分班学生显示统一设置的且已发布、否则对学生所在分班已发布的作业
+ if @user_course_identity < Course::STUDENT
+ @homework_commons = @homework_commons
+ elsif @user_course_identity == Course::STUDENT
+ if @member.try(:course_group_id).to_i == 0
+ @homework_commons = @homework_commons.homework_published.unified_setting
+ else
+ not_homework_ids = @course.homework_group_settings.none_published.where("course_group_id = #{@member.try(:course_group_id)}").pluck(:homework_common_id)
+
+ @homework_commons = @homework_commons.where.not(id: not_homework_ids).homework_published
+ end
+ else
+ @homework_commons = @homework_commons.homework_published.unified_setting
+ end
+
+ @published_count = @user_course_identity < Course::STUDENT ? @homework_commons.homework_published.size :
+ @homework_commons.size
+
+ unless search.blank?
+ @homework_commons = @homework_commons.where("homework_commons.name like ?", "%#{search}%")
+ end
+
+ unless order.blank?
+ case order
+ when '1'
+ sql_str = %Q(homework_detail_manuals.comment_status = #{order} and homework_commons.end_time > '#{Time.now}')
+ when '2'
+ sql_str = %Q(allow_late = 1 and homework_commons.end_time < '#{Time.now}' and (late_time is null or late_time > '#{Time.now}'))
+ when '3'
+ sql_str = %Q(homework_detail_manuals.comment_status = #{order} and homework_detail_manuals.evaluation_end > '#{Time.now}')
+ when '4'
+ sql_str = %Q((homework_detail_manuals.comment_status = #{order} and homework_detail_manuals.appeal_time > '#{Time.now}'))
+ when '5'
+ sql_str = %Q((homework_detail_manuals.comment_status = #{order} or (anonymous_comment = 0 and homework_commons.end_time <= '#{Time.now}')))
+ else
+ sql_str = %Q(homework_detail_manuals.comment_status = #{order})
+ end
+ @homework_commons = @homework_commons.joins(:homework_detail_manual).where(sql_str)
+ end
+ @homework_commons = @homework_commons.order("IF(ISNULL(homework_commons.publish_time),0,1), homework_commons.publish_time DESC,
+ homework_commons.created_at DESC")
+
+ @task_count = @homework_commons.size
+
+ @homework_commons = @homework_commons.page(page).per(15)
+
+ if @homework_type == 4
+ if @user_course_identity == Course::STUDENT
+ @homework_commons = @homework_commons.includes(:homework_detail_manual, :homework_group_settings, :shixuns,
+ student_works: [myshixun: [:games]])
+ else
+ @homework_commons = @homework_commons.includes(:homework_detail_manual, :homework_group_settings, :shixuns, :student_works)
+ end
+ else
+ @homework_commons = @homework_commons.includes(:homework_detail_manual, :homework_group_settings, :homework_detail_group,
+ :student_works)
+ end
+ end
+
+ def works_list
+ @current_user = current_user
+ @member = @course.course_member(@current_user.id)
+
+ @shixun = @homework.shixuns.take if @homework.homework_type == "practice"
+
+ student_works = @homework.all_works
+ @all_member_count = student_works.count
+ if @homework.publish_time.nil? || @homework.publish_time > Time.now
+ @student_works = []
+ respond_to do |format|
+ format.json
+ format.xlsx{
+ normal_status(-1,"作业未发布")
+ }
+ format.zip{
+ normal_status(-1,"作业未发布")
+ }
+ end
+ else
+ if @user_course_identity == Course::STUDENT
+ @work = @homework.user_work(current_user.id)
+
+ # 学生已提交作品且补交(提交)已截止、作品公开、非匿评阶段
+ if @work.work_status > 0 && @homework.work_public &&
+ ((!@homework.anonymous_comment && @homework.end_or_late) || @homework_detail_manual.comment_status > 4)
+ @student_works = student_works.where("user_id != #{@work.id}")
+
+ # 匿评、申诉阶段只能看到分配给自己的匿评作品
+ elsif @work.work_status > 0 && @homework.anonymous_comment && @homework_detail_manual.comment_status > 2
+ @is_evaluation = true
+ @student_works = student_works.joins(:student_works_evaluation_distributions).where(
+ "student_works_evaluation_distributions.user_id = #{@current_user.id}")
+ else
+ @student_works = []
+ end
+ elsif @user_course_identity < Course::STUDENT
+ @student_works = @homework.teacher_works(@current_user.id)
+ @all_member_count = @student_works.count
+ elsif @user_course_identity > Course::STUDENT && @homework.work_public
+ @student_works = student_works
+ else
+ @student_works = []
+ end
+
+ unless @student_works.size == 0
+ # 教师评阅搜索 0: 未评, 1 已评
+ unless params[:teacher_comment].blank?
+ student_work_ids = StudentWorksScore.where(student_work_id: @student_works.map(&:id)).pluck(:student_work_id)
+ if params[:teacher_comment].to_i == 0
+ @student_works = @student_works.where.not(id: student_work_ids)
+ elsif params[:teacher_comment].to_i == 1
+ @student_works = @student_works.where(id: student_work_ids)
+ end
+ end
+
+ # 作品状态 0: 未提交, 1 按时提交, 2 延迟提交
+ unless params[:work_status].blank?
+ @student_works = @student_works.where(work_status: params[:work_status])
+ end
+
+ # 分班情况
+ unless params[:course_group].blank?
+ group_user_ids = @course.students.where(course_group_id: params[:course_group]).pluck(:user_id)
+ # 有分组只可能是老师身份查看列表
+ @student_works = @student_works.where(user_id: group_user_ids)
+ end
+
+ # 输入姓名和学号搜索
+ # TODO user_extension 如果修改 请调整
+ unless params[:search].blank?
+ @student_works = @student_works.joins(user: :user_extension).where("concat(lastname, firstname) like ?
+ or student_id like ?", "%#{params[:search]}%", "%#{params[:search]}%")
+ end
+
+ # 排序
+ rorder = params[:order] || "update_time"
+ b_order = params[:b_order] || "desc"
+ if rorder == "update_time" || rorder == "work_score"
+ @student_works = @student_works.order("student_works.#{rorder} #{b_order}")
+ elsif rorder == "student_id"
+ @student_works = @student_works.joins(user: :user_extension).order("user_extensions.#{rorder} #{b_order}")
+ end
+
+ @work_count = @student_works.size
+ @work_excel = @student_works
+
+ # 分页参数
+ page = params[:page] || 1
+ limit = params[:limit] || 20
+ @student_works = @student_works.page(page).per(limit).includes(:student_works_scores)
+ if @homework.homework_type == "practice"
+ @student_works = @student_works.includes(user: :user_extension, myshixun: :games)
+ else
+ @student_works = @student_works.includes(:student_works_scores, :project, user: :user_extension)
+ end
+ end
+ respond_to do |format|
+ format.json
+ format.xlsx{
+ if @user_course_identity >= Course::STUDENT
+ tip_exception(403, "无权限操作")
+ else
+ student_work_to_xlsx(@work_excel,@homework)
+ exercise_export_name = current_user.real_name + "_" + @course.name + "_" + @homework.name + "_" + Time.now.strftime('%Y%m%d_%H%M%S')
+ render xlsx: "#{exercise_export_name.strip.first(30)}",template: "homework_commons/works_list.xlsx.axlsx",locals:
+ {table_columns: @work_head_cells,task_users: @work_cells_column}
+ end
+ }
+ format.zip{
+ if @user_course_identity >= Course::STUDENT
+ tip_exception(403, "无权限操作")
+ else
+ zip_works = @work_excel.where("work_status > 0")
+ status = checkfileSize(zip_works)
+ if status == 0
+ zipfile = zip_homework_common @homework, zip_works
+ file = decode64(zipfile[0][:base64file])
+ send_file "#{OUTPUT_FOLDER}/#{file}", filename: filename_for_content_disposition(file), type: 'application/zip'
+ else
+ tip_exception(status == -1 ? "文件大小超过500M,请通过微信或者QQ联系管理员辅助您打包下载" : "无附件可下载")
+ end
+ end
+ }
+ end
+ end
+ end
+
+ def alter_name
+ tip_exception("作业名称不能为空") if params[:name].blank?
+ @homework.update_attributes(name: params[:name].strip)
+ normal_status("更新成功")
+ end
+
+ def show
+ @user = current_user
+ @attachments = @homework.attachments.where(attachtype: 1)
+ @work = @homework.user_work(current_user.id) if @user_course_identity == Course::STUDENT
+ end
+
+ # 评论列表接口、 包含一级和二级评论的获取
+ def show_comment
+ @page = params[:page] || 1
+ @limit = params[:limit] || 10
+ @parent = params[:parent_id]
+ @current_user = current_user
+
+ @messages = @homework.journals_for_messages
+ @messages_count = @messages.size
+ @parent_messages_count = @messages.parent_comment.size
+
+ if @parent
+ @messages = @messages.where(m_parent_id: @parent)
+ else
+ @messages = @messages.parent_comment
+ end
+
+ @messages = @messages.page(@page).per(@limit).order("created_on desc")
+ end
+
+ def reference_answer
+ # 只有课堂老师 或 作业设置了“公开答案”,则在作业截止时间后(若开启了补交,则补交截止后),提交了作品的学生 才能访问
+ if @homework.view_answer(@user_course_identity, current_user.id)
+ @attachments = @homework.attachments.where(attachtype: 2)
+ else
+ normal_status(403, "无权限访问")
+ end
+ @current_user = current_user
+ @work = @homework.user_work(current_user.id) if @user_course_identity == Course::STUDENT
+ end
+
+ def update_explanation
+ @homework.update_attributes(explanation: params[:explanation])
+ normal_status(0, "更新成功")
+ end
+
+ def new
+ tip_exception("type参数有误") if params[:type].blank? || ![1, 3].include?(params[:type].to_i)
+ @homework_type = params[:type].to_i
+ end
+
+ def create
+ tip_exception("type参数有误") if params[:type].blank? || ![1, 3].include?(params[:type].to_i)
+ @homework_type = params[:type].to_i
+ if @homework_type == 3
+ validate_min_max_num
+ tip_exception("base_on_project参数不能为空") if params[:base_on_project].nil?
+ end
+
+ ActiveRecord::Base.transaction do
+ begin
+ @homework = HomeworkCommon.new(homework_params)
+ @homework.homework_type = @homework_type
+ @homework.user_id = current_user.id
+ @homework.course_id = @course.id
+
+ homework_detail_manual = HomeworkDetailManual.new
+ @homework.homework_detail_manual = homework_detail_manual
+ homework_detail_manual.te_proportion = 0.7
+ homework_detail_manual.ta_proportion = 0.3
+
+ if @homework_type == 3
+ homework_detail_group = HomeworkDetailGroup.new(min_num: params[:min_num].to_i, max_num: params[:max_num].to_i,
+ base_on_project: params[:base_on_project])
+ @homework.homework_detail_group = homework_detail_group
+ end
+
+ if @homework.save!
+ homework_detail_manual.save! if homework_detail_manual
+ homework_detail_group.save! if homework_detail_group
+
+ # 作业描述的附件
+ Attachment.associate_container(params[:attachment_ids], @homework.id, @homework.class) if params[:attachment_ids]
+ # 作业参考答案的附件
+ Attachment.associate_container(params[:reference_attachment_ids], @homework.id, @homework.class, 2) if params[:reference_attachment_ids]
+
+ HomeworksService.new.create_works_list(@homework, @course)
+ else
+ tip_exception("创建失败")
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def edit
+
+ end
+
+ def update
+ if @homework.homework_type == "group"
+ validate_min_max_num
+ tip_exception("base_on_project参数不能为空") if !@homework.has_relate_project && params[:base_on_project].nil?
+ end
+
+ ActiveRecord::Base.transaction do
+ begin
+ @homework.update_attributes(homework_params)
+
+ if @homework.homework_type == "group"
+ homework_detail_group = @homework.homework_detail_group
+ param_min = params[:min_num].to_i
+ param_max = params[:max_num].to_i
+ homework_detail_group.min_num = @homework.has_commit_work ? (param_min > homework_detail_group.min_num ? homework_detail_group.min_num :
+ param_min) : param_min
+ homework_detail_group.max_num = @homework.has_commit_work ? (param_max < homework_detail_group.max_num ? homework_detail_group.max_num :
+ param_max) : param_max
+ homework_detail_group.base_on_project = params[:base_on_project] unless @homework.has_relate_project
+ homework_detail_group.save!
+ end
+ # 作业描述的附件
+ Attachment.associate_container(params[:attachment_ids], @homework.id, @homework.class) if params[:attachment_ids]
+ # 作业参考答案的附件
+ Attachment.associate_container(params[:reference_attachment_ids], @homework.id, @homework.class, 2) if params[:reference_attachment_ids]
+
+ normal_status(0, "更新成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def settings
+ @user = current_user
+ @work = @homework.user_work(current_user.id) if @user_course_identity == Course::STUDENT
+ end
+
+ def update_settings
+ begin
+ # 课堂结束后不能再更新
+ unless @course.is_end
+
+ # 作业未发布时,unified_setting参数不能为空
+ if @homework.publish_time.nil? || @homework.publish_time > Time.now
+ tip_exception("缺少统一设置的参数") if params[:unified_setting].nil?
+ if params[:unified_setting] || @course.course_groups_count == 0
+ tip_exception("发布时间不能为空") if params[:publish_time].blank?
+ tip_exception("截止时间不能为空") if params[:end_time].blank?
+ tip_exception("发布时间不能早于当前时间") if params[:publish_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S")
+ tip_exception("截止时间不能早于当前时间") if params[:end_time] <= Time.now.strftime("%Y-%m-%d %H:%M:%S")
+ tip_exception("截止时间不能早于发布时间") if params[:publish_time] > params[:end_time]
+ tip_exception("截止时间不能早于课堂结束时间") if @course.end_date.present? && params[:end_time] > @course.end_date.end_of_day
+
+ @homework.unified_setting = 1
+ @homework.homework_group_settings.destroy_all
+ @homework.publish_time = params[:publish_time]
+ # 截止时间为空时取发布时间后一个月
+ @homework.end_time = params[:end_time]
+
+ else
+ tip_exception("分班发布设置不能为空") if params[:group_settings].blank?
+ # 创建作业的分班设置
+ create_homework_group_settings @homework
+
+ setting_group_ids = []
+
+ params[:group_settings].each do |setting|
+ tip_exception("分班id不能为空") if setting[:group_id].length == 0
+ tip_exception("发布时间不能为空") if setting[:publish_time].blank?
+ tip_exception("截止时间不能为空") if setting[:end_time].blank?
+ tip_exception("发布时间不能早于当前时间") if setting[:publish_time] <= strf_time(Time.now)
+ tip_exception("截止时间不能早于当前时间") if setting[:end_time] <= strf_time(Time.now)
+ tip_exception("截止时间不能早于发布时间") if setting[:publish_time] > setting[:end_time]
+ tip_exception("截止时间不能早于课堂结束时间") if @course.end_date.present? && setting[:end_time] > @course.end_date.end_of_day
+
+
+ publish_time = setting[:publish_time] == "" ? Time.now : setting[:publish_time]
+ # 截止时间为空时取发布时间后一个月
+ end_time = setting[:end_time] == "" ? Time.at(publish_time.to_time.to_i+30*24*3600) : setting[:end_time]
+ HomeworkGroupSetting.where(homework_common_id: @homework.id, course_group_id: setting[:group_id]).
+ update_all(publish_time: publish_time, end_time: end_time)
+ setting_group_ids << setting[:group_id]
+ end
+
+ # 未设置的分班:发布时间和截止时间都为nil
+ HomeworkGroupSetting.where.not(course_group_id: setting_group_ids).where(homework_common_id: @homework.id).
+ update_all(publish_time: nil, end_time: nil)
+
+ # 记录已发布需要发消息的分班
+ publish_group_ids = HomeworkGroupSetting.where(homework_common_id: @homework.id).group_published.pluck(:course_group_id)
+
+ @homework.unified_setting = 0
+ @homework.publish_time = @homework.min_group_publish_time
+ @homework.end_time = @homework.max_group_end_time
+ end
+
+ # 如果作业立即发布则更新状态、发消息
+ if @homework.publish_time <= Time.now and @homework_detail_manual.comment_status == 0
+ @homework_detail_manual.comment_status = 1
+ send_tiding = true
+ end
+
+ # 作业在"提交中"状态时
+ else
+ if @homework.end_time > Time.now && @homework.unified_setting
+ tip_exception("截止时间不能为空") if params[:end_time].blank?
+ tip_exception("截止时间不能早于当前时间") if params[:end_time] <= strf_time(Time.now)
+ tip_exception("截止时间不能早于课堂结束时间") if @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day)
+
+ @homework.end_time = params[:end_time]
+
+ elsif !@homework.unified_setting
+ create_homework_group_settings @homework
+ tip_exception("分班发布设置不能为空") if params[:group_settings].blank?
+ params[:group_settings].each do |setting|
+ group_settings = HomeworkGroupSetting.where(homework_common_id: @homework.id, course_group_id: setting[:group_id])
+
+ tip_exception("分班id不能为空") if setting[:group_id].length == 0
+ tip_exception("发布时间不能为空") if setting[:publish_time].blank?
+ tip_exception("截止时间不能为空") if setting[:end_time].blank?
+ # 如果该发布规则 没有已发布的分班则需判断发布时间
+ tip_exception("发布时间不能早于当前时间") if setting[:publish_time] <= strf_time(Time.now) && group_settings.group_published.count == 0
+
+ tip_exception("截止时间不能早于当前时间") if setting[:end_time] <= strf_time(Time.now)
+ tip_exception("截止时间不能早于发布时间") if setting[:publish_time] > setting[:end_time]
+ tip_exception("截止时间不能早于课堂结束时间") if setting[:end_time] > strf_time(@course.end_date.end_of_day)
+
+ group_settings.none_published.update_all(publish_time: setting[:publish_time])
+ group_settings.none_end.update_all(end_time: setting[:end_time])
+ end
+
+ @homework.end_time = @homework.max_group_end_time
+ end
+ end
+
+ # 补交设置
+ tip_exception("缺少allow_late参数") if params[:allow_late].nil?
+ tip_exception("缺少late_penalty参数") if params[:allow_late] && params[:late_penalty].blank?
+ tip_exception("缺少late_time参数") if params[:allow_late] && params[:late_time].blank?
+
+ current_late_penalty = @homework.late_penalty
+ if params[:allow_late]
+ tip_exception("补交结束时间必须晚于截止时间") if params[:late_time] <= strf_time(@homework.end_time)
+ tip_exception("补交结束时间不能晚于课堂结束时间") if @course.end_date.present? && params[:late_time] >
+ strf_time(@course.end_date.end_of_day)
+ tip_exception("迟交扣分应为正整数") if params[:late_penalty] && params[:late_penalty].to_i < 0
+
+ @homework.allow_late = true
+ @homework.late_time = params[:late_time]
+ @homework.late_penalty = params[:late_penalty].to_i
+ else
+ @homework.allow_late = false
+ @homework.late_penalty = 0
+ @homework.late_time = nil
+ end
+
+ # 迟交扣分有变动则更新迟交学生的成绩
+ late_penalty_change = @homework.late_penalty != current_late_penalty
+
+ if @homework.homework_type == "practice"
+
+ # 实训作业的评分设置
+ tip_exception("缺少answer_open_evaluation参数") if params[:answer_open_evaluation].nil?
+ tip_exception("缺少work_efficiency参数") if params[:work_efficiency].nil?
+ tip_exception("缺少eff_score参数") if params[:work_efficiency] && params[:eff_score].blank?
+ tip_exception("效率分应为正整数") if params[:eff_score] && params[:eff_score].to_i < 0
+ tip_exception("缺少shixun_evaluation参数") if params[:shixun_evaluation].blank?
+ tip_exception("缺少challenge_settings参数") if params[:challenge_settings].blank?
+ # tip_exception("缺少challenge_id参数") if params[:challenge_settings][:challenge_id].blank?
+ # tip_exception("缺少challenge_score参数") if params[:challenge_settings][:challenge_score].blank?
+ # tip_exception("challenge_id参数的长度与challenge_score参数的长度不匹配") if
+ # params[:challenge_settings][:challenge_score].length != params[:challenge_settings][:challenge_id].length
+
+ current_eff_score = @homework.eff_score
+ @homework.work_efficiency = params[:work_efficiency]
+ @homework.eff_score = params[:work_efficiency] ? params[:eff_score].to_i : 0
+
+ update_eff_score = current_eff_score != @homework.eff_score
+
+ if @homework_detail_manual.answer_open_evaluation != params[:answer_open_evaluation]
+ @homework_detail_manual.answer_open_evaluation = params[:answer_open_evaluation]
+ score_change = true
+ end
+
+ @homework_detail_manual.shixun_evaluation = params[:shixun_evaluation].to_i
+
+ if params[:challenge_settings]
+ params[:challenge_settings].each do |challenge|
+ setting = @homework.homework_challenge_settings.find_by(challenge_id: challenge[:challenge_id])
+ score = challenge[:challenge_score]
+ if setting && setting.score != score
+ score_change = true
+ setting.update_attributes(score: score)
+ else
+ score_change = true
+ HomeworkChallengeSetting.create!(homework_common_id: @homework.id, challenge_id: challenge[:challenge_id],
+ shixun_id: @homework.homework_commons_shixun.try(:shixun_id), score: score)
+ end
+ end
+
+ if @homework.homework_challenge_settings.where.not(challenge_id: params[:challenge_settings].pluck(:challenge_id)).count > 0
+ score_change = true
+ @homework.homework_challenge_settings.where.not(challenge_id: params[:challenge_settings].pluck(:challenge_id)).destroy_all
+ end
+ end
+
+ # 公开设置
+ tip_exception("缺少score_open参数") if params[:score_open].nil?
+ @homework.score_open = params[:score_open]
+
+ @homework.save!
+ if score_change
+ @homework.student_works.has_committed.each do |student_work|
+ HomeworksService.new.set_shixun_final_score student_work
+ end
+ end
+
+ # 更新所有学生的效率分(作业允许补交且补交已截止 或者 作业不允许补交且提交已截止)
+ if (score_change || update_eff_score) && @homework.end_or_late
+ HomeworksService.new.update_student_eff_score HomeworkCommon.find_by(id: @homework.id)
+ end
+
+ # 更新迟交扣分
+ if !(score_change || update_eff_score) && late_penalty_change
+ @homework.student_works.where(work_status: 2).each do |work|
+ work.late_penalty = @homework.late_penalty
+ work.save!
+ end
+ end
+
+ unless @homework.allow_late
+ @homework.student_works.where(work_status: 2).update_all(work_status: 1)
+ end
+
+ @homework_detail_manual.save!
+ @homework.save!
+ else
+
+ # 普通和分组作业的匿评设置
+ current_absence_penalty = @homework_detail_manual.absence_penalty
+ current_appeal_penalty = @homework_detail_manual.appeal_penalty
+
+ # 匿评未开启前可以更新:是否开启匿评、匿评开始时间、匿评数
+ if @homework_detail_manual.comment_status < 3
+ tip_exception("缺少anonymous_comment参数") if params[:anonymous_comment].nil?
+ # anonymous_comment :true 是启用,false 是不启用
+ if params[:anonymous_comment]
+ tip_exception("匿评开启时间不能为空") if params[:evaluation_start].blank?
+ tip_exception("匿评开启时间不能早于截止时间") if params[:evaluation_start] <= strf_time(@homework.end_time)
+ tip_exception("匿评结束时间不能为空") if params[:evaluation_end].blank?
+ tip_exception("匿评截止时间不能早于匿评开启时间") if params[:evaluation_end] <= params[:evaluation_start]
+ tip_exception("匿评截止时间不能晚于课堂结束时间") if @course.end_date.present? && params[:evaluation_end] >
+ strf_time(@course.end_date.end_of_day)
+ tip_exception("匿评数必须为正整数") if params[:evaluation_num].blank? || params[:evaluation_num].to_i < 1
+ tip_exception("缺评扣分不能为空") if params[:absence_penalty].blank?
+ tip_exception("缺评扣分不能小于0") if params[:absence_penalty].to_i < 0
+ tip_exception("缺评扣分不能大于100") if params[:absence_penalty].to_i > 100
+ end
+
+ @homework.anonymous_comment = params[:anonymous_comment]
+ @homework_detail_manual.evaluation_start = !@homework.anonymous_comment ? nil : params[:evaluation_start]
+ @homework_detail_manual.evaluation_num = !@homework.anonymous_comment ? 0 : params[:evaluation_num]
+
+ # 不启用匿评时还原申诉设置和教师、助教的评分比例
+ unless @homework.anonymous_comment
+ @homework.anonymous_appeal = false
+ @homework_detail_manual.appeal_time = nil
+ @homework_detail_manual.appeal_penalty = 0
+ @homework_detail_manual.te_proportion = 1
+ @homework_detail_manual.ta_proportion = 0
+ end
+
+ end
+
+ # 匿评未截止时可以更新匿评结束时间
+ if @homework_detail_manual.comment_status < 4
+ tip_exception("匿评结束时间不能为空") if @homework.anonymous_comment && params[:evaluation_end].blank?
+ tip_exception("匿评截止时间不能早于匿评开启时间") if @homework.anonymous_comment &&
+ params[:evaluation_end] <= params[:evaluation_start]
+ tip_exception("匿评截止时间不能晚于课堂结束时间") if @homework.anonymous_comment &&
+ @course.end_date.present? && params[:evaluation_end] > strf_time(@course.end_date.end_of_day)
+
+ @homework_detail_manual.evaluation_end = !@homework.anonymous_comment ? nil : params[:evaluation_end]
+ end
+
+ # 作业未结束可以更新缺评扣分
+ tip_exception("缺评扣分不能为空") if @homework.anonymous_comment && params[:absence_penalty].blank?
+ tip_exception("缺评扣分不能小于0") if @homework.anonymous_comment && params[:absence_penalty].to_i < 0
+ tip_exception("缺评扣分不能大于100") if @homework.anonymous_comment && params[:absence_penalty].to_i > 100
+ @homework_detail_manual.absence_penalty = !@homework.anonymous_comment ? 0 : params[:absence_penalty].to_i
+
+
+ # 匿评申诉设置
+ # 匿评申诉未开启前可以更新:是否启用匿评申诉
+ if @homework_detail_manual.comment_status < 4 && @homework.anonymous_comment
+ tip_exception("缺少anonymous_appeal参数") if params[:anonymous_appeal].nil?
+ @homework.anonymous_appeal = params[:anonymous_appeal]
+ end
+
+
+ # 匿评申诉未结束前可以更新:匿评申诉结束时间
+ if @homework_detail_manual.comment_status < 5
+ tip_exception("匿评申诉结束时间不能为空") if @homework.anonymous_appeal && params[:appeal_time].blank?
+ tip_exception("匿评开启时间不能早于匿评截止时间") if @homework.anonymous_appeal &&
+ params[:appeal_time] <= strf_time(@homework_detail_manual.evaluation_end)
+ tip_exception("匿评申诉结束不能晚于课堂结束时间") if @homework.anonymous_appeal &&
+ @course.end_date.present? && params[:appeal_time] > strf_time(@course.end_date.end_of_day)
+
+ @homework_detail_manual.appeal_time = @homework.anonymous_appeal ? params[:appeal_time] : nil
+ end
+
+ # 作业未结束可以更新违规匿评扣分
+ tip_exception("违规匿评扣分不能为空") if @homework.anonymous_appeal && params[:appeal_penalty].blank?
+ tip_exception("违规匿评扣分不能小于0") if @homework.anonymous_appeal && params[:appeal_penalty].to_i < 0
+ tip_exception("违规匿评扣分不能大于100") if @homework.anonymous_appeal && params[:appeal_penalty].to_i > 100
+ @homework_detail_manual.appeal_penalty = @homework.anonymous_appeal ? params[:appeal_penalty].to_i : 0
+
+ # 如果缺评扣分的设置有变更且匿评已截止
+ absence_penalty_change = current_absence_penalty != @homework_detail_manual.absence_penalty &&
+ @homework_detail_manual.comment_status >= 4
+ # 如果违规匿评扣分的设置有变更且匿评已截止
+ appeal_penalty_change = current_appeal_penalty != @homework_detail_manual.appeal_penalty &&
+ @homework_detail_manual.comment_status >= 4
+
+ # 评分设置
+ tip_exception("助教评分模式不能为空") if params[:ta_mode].blank?
+
+ # 助教评分模式的变更
+ ta_mode_change = @homework_detail_manual.ta_mode != params[:ta_mode].to_i
+ @homework_detail_manual.ta_mode = params[:ta_mode].to_i
+
+ # 最终成绩组成
+ tip_exception("最终成绩组成模式不能为空") if params[:final_mode].nil?
+
+ final_mode_change = @homework_detail_manual.final_mode != params[:final_mode]
+ @homework_detail_manual.final_mode = params[:final_mode]
+ if !@homework_detail_manual.final_mode
+ tip_exception("教师评分比例不能为空") if params[:te_proportion].blank?
+ te_proportion = params[:te_proportion].to_f.round(2)
+ tip_exception("教师评分比例不能小于零") if te_proportion < 0
+ tip_exception("助教评分比例不能为空") if params[:ta_proportion].blank?
+ ta_proportion = params[:ta_proportion].to_f.round(2)
+ tip_exception("助教评分比例不能小于零") if ta_proportion < 0
+ if !@homework.anonymous_comment
+ tip_exception("评分比例之和不能大于100") if (te_proportion + ta_proportion) > 1.0
+ else
+ tip_exception("学生评分比例不能为空") if params[:st_proportion].blank?
+ st_proportion = params[:st_proportion].to_f.round(2)
+ tip_exception("学生评分比例不能小于零") if st_proportion < 0
+ tip_exception("评分比例之和不能大于100") if (te_proportion + ta_proportion + st_proportion) > 1.0
+ end
+
+ proportion_change = @homework_detail_manual.te_proportion.round(2) != te_proportion ||
+ @homework_detail_manual.ta_proportion.round(2) != ta_proportion
+ @homework_detail_manual.te_proportion = te_proportion
+ @homework_detail_manual.ta_proportion = ta_proportion
+ else
+ @homework_detail_manual.te_proportion = 1
+ @homework_detail_manual.ta_proportion = 0
+ end
+
+ # 公开属性设置
+ tip_exception("缺少work_public参数") if params[:work_public].nil?
+ tip_exception("缺少score_open参数") if params[:score_open].nil?
+ tip_exception("缺少answer_public参数") if params[:answer_public].nil?
+ @homework.work_public = params[:work_public]
+ @homework.score_open = params[:score_open]
+ @homework.answer_public = params[:answer_public]
+
+ @homework_detail_manual.save!
+ @homework.save!
+
+ # 迟交扣分、缺评扣分、违规匿评扣分、助教评分模式变更、最终成绩组成、评分比例变更都需要更新学生成绩
+ if late_penalty_change || absence_penalty_change || appeal_penalty_change || ta_mode_change ||
+ final_mode_change || proportion_change
+
+ student_works = @homework.student_works.has_committed
+ work_ids = student_works.pluck(:id)
+
+ student_works.each do |student_work|
+ # 迟交扣分
+ student_work.late_penalty = student_work.work_status == 1 ? 0 : @homework.late_penalty
+
+ # 缺评扣分的更新 如果之前的作业缺评扣分为0,则需重新计算缺评次数
+ if absence_penalty_change
+ absence_penalty_count = current_absence_penalty == 0 ? student_work.absence_count :
+ (student_work.absence_penalty / current_absence_penalty).to_i
+ student_work.absence_penalty = absence_penalty_count * @homework_detail_manual.absence_penalty
+ end
+
+ # 违规匿评扣分 如果之前的作业违规扣分为0,则需重新计算违规匿评次数
+ if appeal_penalty_change
+ appeal_penalty_count = current_appeal_penalty == 0 ? student_work.appeal_count :
+ (student_work.appeal_penalty / current_appeal_penalty).to_i
+ student_work.appeal_penalty = appeal_penalty_count * @homework_detail_manual.appeal_penalty
+ end
+
+ # 助教模式变更且有助教评分记录时才更新
+ if ta_mode_change && student_work.student_works_scores.where("reviewer_role = 2 AND score IS NOT NULL").count > 0
+ student_work.teaching_asistant_score = student_work.ta_score @homework_detail_manual.ta_mode
+ end
+
+ student_work.save!
+ end
+ end
+
+ end
+
+ HomeworkCommonPushNotifyJob.perform_later(@homework.id, publish_group_ids) if send_tiding
+ normal_status(0, "更新成功")
+ else
+ tip_exception("课堂已结束不能再更新")
+ end
+ rescue Exception => e
+ uid_logger(e.backtrace)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ # 选用实训
+ def shixuns
+ search = params[:search]
+ type = params[:type]
+ # 超级管理员用户显示所有未隐藏的实训、非管理员显示所有已发布的实训(对本单位公开且未隐藏未关闭)
+ if current_user.admin?
+ @shixuns = Shixun.unhidden
+ else
+ none_shixun_ids = ShixunSchool.where("school_id != #{current_user.school_id}").pluck(:shixun_id)
+
+ @shixuns = Shixun.where.not(id: none_shixun_ids).unhidden
+ end
+
+ # 实训的所有标签
+ @tags = TagRepertoire.select([:id, :name]).joins(:shixuns).where(shixuns: {id: @shixuns}).distinct
+
+ if params[:search] && params[:search].strip != ""
+ @shixuns = @shixuns.joins(:user).where("shixuns.name like ? or concat(users.lastname, users.firstname) like ?",
+ "%#{search}%", "%#{search}%").distinct
+ end
+
+ unless type.blank? || type == "all"
+ @shixuns = @shixuns.joins(:shixun_tag_repertoires).where(shixun_tag_repertoires: {tag_repertoire_id: type}).distinct
+ end
+
+ @shixuns = @shixuns.select([:id, :name, :status, :myshixuns_count, :identifier]).reorder("shixuns.created_at desc")
+ @shixuns_count = @shixuns.size
+
+ ## 分页参数
+ page = params[:page] || 1
+ @shixuns = @shixuns.page(page).per(10)
+
+ @main_catrgory = @course.course_modules.where(module_type: "shixun_homework")
+ @homework_category = @main_catrgory.take.course_second_categories
+ end
+
+ def create_shixun_homework
+ tip_exception("请至少选择一个实训") if params[:shixun_ids].blank?
+ shixuns = Shixun.where(id: params[:shixun_ids]).reorder("id desc")
+ @homework_ids = []
+ unless params[:category_id].blank?
+ @category = @course.course_second_categories.find_by(id: params[:category_id], category_type: "shixun_homework")
+ end
+ ActiveRecord::Base.transaction do
+ begin
+ shixuns.each do |shixun|
+ homework = HomeworksService.new.create_homework shixun, @course, @category, current_user
+ @homework_ids << homework.id
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("创建失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 选用实训课程
+ def subjects
+ search = params[:search]
+ type = params[:type]
+ # 显示所有未隐藏的、已发布的实训课程
+ @subjects = Subject.select([:id, :name, :status, :repertoire_id]).visible.unhidden
+
+ @tags = Repertoire.select([:id, :name]).where(id: @subjects.pluck(:repertoire_id).uniq).order("updated_at desc")
+
+ if params[:search] && params[:search].strip != ""
+ @subjects = @subjects.joins(:user).where("subjects.name like ? or concat(users.lastname, users.firstname) like ?",
+ "%#{search}%", "%#{search}%")
+ end
+
+ unless type.blank? || type == "all"
+ @subjects = @subjects.where(repertoire_id: type)
+ end
+
+ @subjects = @subjects.reorder("subjects.created_at desc")
+ @subjects_count = @subjects.size
+
+ ## 分页参数
+ page = params[:page] || 1
+ @subjects = @subjects.page(page).per(10)
+
+ @subjects = @subjects.includes(:shixuns)
+ end
+
+ def create_subject_homework
+ tip_exception("请至少选择一个实训课程") if params[:subject_ids].blank?
+ subjects = Subject.where(id: params[:subject_ids], status: 2).includes(stages: :shixuns).reorder("id desc")
+ @homework_ids = []
+
+ none_shixun_ids = ShixunSchool.where("school_id != #{current_user.school_id}").pluck(:shixun_id)
+
+ course_module = @course.course_modules.find_by(module_type: "shixun_homework")
+ ActiveRecord::Base.transaction do
+ begin
+ subjects.each do |subject|
+
+ subject.stages.each do |stage|
+
+ # 为实训作业创建与stage同名的子目录
+ category = CourseSecondCategory.find_by(name: stage.name, course_id: @course.id, category_type: "shixun_homework") ||
+ CourseSecondCategory.create!(name: stage.name, course_id: @course.id, category_type: "shixun_homework",
+ course_module_id: course_module.id, position: course_module.course_second_categories.count + 1)
+
+ # 去掉不对当前用户的单位公开的实训,已发布的实训
+ stage.shixuns.where.not(shixuns: {id: none_shixun_ids}).unhidden.each do |shixun|
+ homework = HomeworksService.new.create_homework shixun, @course, category, current_user
+ @homework_ids << homework.id
+ end
+ end
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("创建失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def publish_groups
+ @current_user = current_user
+ if @homework.publish_immediately @current_user
+ # 可立即发布的分班:当前用户管理的分班去除已发布的分班
+ group_ids = @course.charge_group_ids(@current_user) - @homework.homework_group_settings.group_published.pluck(:course_group_id)
+ @course_groups = @course.course_groups.where(id: group_ids)
+ else
+ tip_exception("没有可发布的分班")
+ end
+ end
+
+ def publish_homework
+ tip_exception("请至少选择一个分班") if params[:group_ids].blank? && @course.course_groups.size != 0
+ tip_exception("缺少截止时间参数") if params[:end_time].blank?
+ tip_exception("截止时间必须晚于当前时间") if params[:end_time] <= strf_time(Time.now)
+
+ homeworks = @course.homework_commons.where(id: params[:homework_ids])
+ homeworks = homeworks.includes(:homework_group_settings, :homework_detail_manual)
+
+ charge_group_ids = @course.charge_group_ids(current_user)
+ publish_groups = charge_group_ids & params[:group_ids] if params[:group_ids]
+
+ ActiveRecord::Base.transaction do
+ begin
+ homeworks.each do |homework|
+ # 作业未发布时
+ if homework.homework_detail_manual.try(:comment_status) == 0
+ if !params[:group_ids].blank?
+
+ # 全选即统一设置,unified_setting为true
+ if @course.course_groups.where(id: publish_groups).size == @course.course_groups.size
+ homework.homework_group_settings.destroy_all
+ homework.unified_setting = true
+ else
+ homework.unified_setting = false
+ # 创建作业分班设置:homework_group_setting
+ create_homework_group_settings(homework)
+
+ # 选中的分班设置的发布时间改为当前时间,截止时间不为空的保持原状,为空的改为一个月后
+ homework.homework_group_settings.where(course_group_id: publish_groups).update_all(publish_time: Time.now)
+ homework.homework_group_settings.where(course_group_id: publish_groups, end_time: nil).
+ update_all(end_time: params[:end_time])
+ # 发消息
+ tiding_group_ids = publish_groups
+ end
+ else
+ homework.homework_group_settings.destroy_all
+ # students = @course.students
+ end
+
+ homework.publish_time = Time.now
+
+ # 截止时间不为空的保持原状,为空的改为一个月后, 非统一设置的更新为最大分班截止时间
+ if homework.end_time.nil?
+ homework.end_time = params[:end_time]
+ elsif homework.max_group_end_time
+ homework.end_time = homework.max_group_end_time
+ end
+ homework.homework_detail_manual.update_attribute('comment_status', 1)
+
+ if homework.course_acts.size == 0
+ homework.course_acts << CourseActivity.new(user_id: homework.user_id, course_id: homework.course_id)
+ end
+
+ HomeworkCommonPushNotifyJob.perform_later(homework.id, tiding_group_ids)
+ else
+ create_homework_group_settings(homework)
+
+ none_publish_settings = homework.homework_group_settings.where(course_group_id: publish_groups).none_published
+ none_publish_settings.update_all(publish_time: Time.now)
+ none_publish_settings.where(end_time: nil).update_all(end_time: params[:end_time])
+ if homework.max_group_end_time
+ homework.end_time = homework.max_group_end_time
+ end
+ HomeworkCommonPushNotifyJob.perform_later(homework.id, none_publish_settings.pluck(:course_group_id))
+ end
+ if homework.end_time > Time.now && homework.homework_detail_manual.try(:comment_status) > 1
+ homework.homework_detail_manual.update_attribute("comment_status", 1)
+ end
+
+ # 补交结束时间
+ homework.late_time = Time.at(homework.end_time.to_i + 30*24*3600) if homework.allow_late && homework.late_time.nil?
+
+ homework.save!
+
+ HomeworkPublishUpdateWorkStatusJob.perform_later(tiding_group_ids, homework.id)
+ end
+ normal_status(0, "发布成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("发布失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def end_groups
+ @current_user = current_user
+ if @homework.end_immediately @current_user
+ # 可立即截止的分班:统一设置则是用户管理的所有分班,否则是当前用户管理的分班中已发布且未截止的
+ charge_group_ids = @course.charge_group_ids(@current_user) # 当前用户管理的分班
+ group_ids = @homework.unified_setting ? charge_group_ids :
+ @homework.homework_group_settings.where(course_group_id: charge_group_ids).none_end.pluck(:course_group_id)
+ @course_groups = @course.course_groups.where(id: group_ids)
+ else
+ tip_exception("没有可截止的分班")
+ end
+ end
+
+ def end_homework
+ tip_exception("请至少选择一个分班") if params[:group_ids].blank? && @course.course_groups.size != 0
+
+ time = Time.now.strftime("%Y-%m-%d %H:%M:%S")
+
+ # 已发布且未截止的作业才能立即截止
+
+ homeworks = @course.homework_commons.where(id: params[:homework_ids])
+ homeworks = homeworks.published_no_end.includes(:homework_group_settings, :homework_detail_manual, :homework_challenge_settings)
+ course_students = @course.students
+ charge_group_ids = @course.charge_group_ids(current_user)
+ end_groups = charge_group_ids & params[:group_ids] if params[:group_ids]
+
+ ActiveRecord::Base.transaction do
+ begin
+ homeworks.each do |homework|
+ homework_detail_manual = homework.homework_detail_manual
+
+ # 分组设置
+ if !params[:group_ids].blank?
+ # 确保之前是统一设置或者有新创建的分班的数据一致性
+ create_homework_group_settings(homework)
+
+ homework.unified_setting = false if homework.unified_setting && end_groups.length != @course.course_groups_count
+
+ # 已发布且未截止的分班
+ none_end_settings = homework.homework_group_settings.where(course_group_id: end_groups).published_no_end
+
+ none_end_settings.update_all(end_time: time)
+ student_works = homework.student_works.where(user_id: course_students.where(course_group_id: none_end_settings.
+ pluck(:course_group_id)).pluck(:user_id)).has_committed if homework.homework_type == "practice"
+
+ homework.end_time = homework.max_group_end_time
+ if homework.end_time > time && homework_detail_manual.try(:comment_status) > 1
+ homework_detail_manual.update_attribute("comment_status", 1)
+ end
+
+ # 统一设置
+ elsif homework.unified_setting
+ student_works = homework.student_works.has_committed if homework.homework_type == "practice"
+ homework.end_time = time
+ end
+
+ homework_detail_manual.update_attribute("comment_status", 2) if homework.end_time <= time
+
+ # 实训作业的作品需要计算是否迟交
+ if homework.homework_type == "practice"
+ # shixun = homework.shixuns.first
+ # homework_challenge_settings = homework.homework_challenge_settings
+ unless student_works.blank?
+ student_works.joins(:myshixun).where("myshixuns.status != 1").update_all(late_penalty: homework.late_penalty) if homework.allow_late
+
+=begin
+ student_works.where("work_status != 0").includes(:myshixun).each do |student_work|
+ unless student_work.myshixun.is_complete?
+ student_work.update_attributes(work_status: 2, late_penalty: homework.late_penalty)
+ student_work.late_penalty = homework.late_penalty
+ end
+ HomeworksService.new.set_shixun_final_score student_work, student_work.myshixun, homework_detail_manual.answer_open_evaluation,
+ homework_challenge_settings
+ end
+
+ student_works.where("work_status = 0").each do |student_work|
+ myshixun = Myshixun.where(shixun_id: shixun.id, user_id: student_work.user_id).first
+ if myshixun.present?
+ student_work.update_attributes(work_status: (myshixun.is_complete? ? 1 : 2),
+ late_penalty: myshixun.is_complete? ? 0 : homework.late_penalty,
+ commit_time: myshixun.created_at, myshixun_id: myshixun.id)
+ student_work.late_penalty = myshixun.is_complete? ? 0 : homework.late_penalty
+ HomeworksService.new.set_shixun_final_score student_work, myshixun, homework_detail_manual.answer_open_evaluation,
+ homework_challenge_settings
+ end
+ end
+=end
+
+ # 更新所有学生的效率分(重新取homework确保是更新后的)
+ HomeworksService.new.update_student_eff_score HomeworkCommon.find_by(id: homework.id) if !homework.allow_late && homework.end_time <= time
+ end
+ end
+ homework.save!
+ end
+ normal_status(0, "更新成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("操作失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def set_public
+ tip_exception("仅公开课堂才能公开作业") if @course.is_public == 0
+ homeworks = @course.homework_commons.where(id: params[:homework_ids])
+ homeworks.update_all(is_public: 1)
+ normal_status(0, "更新成功")
+ end
+
+ def choose_category
+ @main_catrgory = @course.course_modules.where(module_type: "shixun_homework")
+ @homework_category = @main_catrgory.take.course_second_categories
+ end
+
+ # 实训作业移动到目录
+ def move_to_category
+ tip_exception("请选择要移动的目录") if params[:new_category_id].blank?
+
+ category = @course.course_second_categories.find_by(id: params[:new_category_id])
+ if params[:new_category_id].to_i == 0 || category.present?
+ homeworks = @course.homework_commons.where(id: params[:homework_ids])
+
+ homeworks.update_all(course_second_category_id: params[:new_category_id])
+ normal_status(0, "更新成功")
+ else
+ normal_status(-1, "目录不存在")
+ end
+ end
+
+ # 删除多个作业
+ def multi_destroy
+ ActiveRecord::Base.transaction do
+ begin
+ homeworks = @course.homework_commons.where(id: params[:homework_ids])
+ homeworks.destroy_all
+
+ # 这些写是因为model中的关联删除无法删除is_delete=0的作品
+ StudentWork.where(homework_common_id: homeworks.pluck(:id)).destroy_all
+ normal_status(0, "删除成功")
+
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("删除失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #加入到题库
+ def add_to_homework_bank
+ homeworks = @course.homework_commons.where(id: params[:homework_ids])
+
+ homeworks.each do |homework|
+ ActiveRecord::Base.transaction do
+ begin
+ homework_bank = current_user.homework_banks.find_by(homework_common_id: homework.id)
+ if homework_bank.present?
+ # 如果作业加入过题库则更新参数
+ if homework_bank.homework_type == 1
+ homework_bank.update_attributes(name: homework.name, description: homework.description,
+ reference_answer: homework.reference_answer, course_list_id: @course.course_list_id)
+ elsif homework_bank.homework_type == 3
+ homework_detail_group = homework.homework_detail_group
+ homework_bank.update_attributes(name: homework.name, description: homework.description,
+ reference_answer: homework.reference_answer, course_list_id: @course.course_list_id,
+ min_num: homework_detail_group.min_num, max_num: homework_detail_group.max_num,
+ base_on_project: homework_detail_group.base_on_project)
+ end
+
+ # 附件的更新
+ homework_bank.attachments.destroy_all
+ homework.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = homework_bank.user_id
+ att.copy_from = attachment.id
+ homework_bank.attachments << att
+ end
+ else
+ new_homework_bank = add_to_homework_bank_f homework
+ new_homework_bank.save!
+ end
+
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("删除失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+ normal_status(0, "加入成功")
+ end
+
+ # 代码查重分班列表
+ def group_list
+ @page = params[:page] || 1
+ @limit = params[:limit] || 10
+ @course_groups = @course.course_groups.page(@page).per(@limit)
+ @ungroup_user_ids = @course.course_members.ungroup_students.pluck(:user_id)
+ end
+
+ # 班级作品查重
+ def homework_code_repeat
+ tip_exception(-1,"分班id不能为空!") if params[:group_ids].nil?
+ shixun = @homework.shixuns.take
+ # 通过代码文件来判断语言
+ language = shixun.challenges.practice_type.pluck(:path).first
+ language = language.split(";")[0].split(".")[1].downcase if language.present?
+ user_lists = []
+ if language.present? && (language == "java" || language == "py")
+ user_ids = @course.course_members.where(course_group_id: params[:group_ids]).distinct(:user_id).pluck(:user_id)
+ challenge_ids = @homework.homework_challenge_settings.pluck(:challenge_id)
+ challenge_ids = challenge_ids.size == 0 ? "(-1)" : "(#{challenge_ids.join(",")})"
+ user_ids.each do |user_id|
+ code_infos = []
+ games = Game.find_by_sql("select games.* from games right join myshixuns ms on games.myshixun_id = ms.id where
+ games.user_id = #{user_id} and games.status = 2 and ms.shixun_id = #{shixun.id} and
+ games.challenge_id in #{challenge_ids}")
+ games.each do |game|
+ game.game_codes.each do |game_code|
+ code_infos << {
+ path: game_code.path,
+ content: Base64.urlsafe_encode64(game_code.new_code.to_s, padding: false),
+ passed_time: game.end_time.try(:strftime, '%Y-%m-%d %H:%M:%S')
+ }
+ end
+ end
+ if code_infos.size != 0
+ user_lists << {
+ user_id: user_id,
+ code_info: code_infos
+ }
+ end
+ end
+ result = ReviewService.check(user_lists, language == "py" ? "python" : "java")
+ if result.status == 0
+ params[:group_ids].each do |group_id|
+ @homework.homework_group_reviews << HomeworkGroupReview.new(:course_group_id => group_id,
+ :user_id => current_user.id,
+ :query_id => result.query_id)
+ end
+ normal_status("代码查重成功")
+ else
+ if result.status == 1
+ tip_exception(-4,"代码查重异常,请稍后重试")
+ else
+ tip_exception(-3,"正在查重,请在几分钟后刷新页面查看结果")
+ end
+ end
+ else
+ tip_exception(-2,"平台目前支持java、python语言的查重
其他语言正在规划中,敬请期待")
+ end
+ end
+
+
+ # 代码查重届结果
+ def code_review_results
+ # 如果有未获取结果的查重操作 则先读取结果
+ get_new_code_reviews_result @homework
+ @current_user = current_user
+
+ # 列表数据
+ rorder = params[:order] || "code_rate"
+ sort = params[:sort] || "desc"
+ page = params[:page] || 1
+ limit = params[:limit] || 15
+ student_works = @homework.student_works.where("work_status > 0")
+ # 按分班id搜索
+ user_ids =
+ if params[:group_ids]
+ # 筛选了分班
+ group_student_ids = @course.course_members.where(course_group_id: params[:group_ids]).pluck(:user_id)
+ student_works.where(:user_id => group_student_ids).pluck(:user_id)
+ else
+ # 如果当前用户有分班 显示分班内的学生,没有则显示全部
+ user_ids = @course.user_group_students(current_user.id).pluck(:user_id)
+ if user_ids.present?
+ student_works.where(:user_id => user_ids).pluck(:user_id)
+ else
+ student_works.pluck(:user_id)
+ end
+ end
+ # 查询作品数总数
+ @all_reviews_count = user_ids.count
+ @users_reviews = @homework.homework_review_results.where("code_rate >= 50.0")
+ .where(:user_id => user_ids).joins(user: :user_extension)
+ # 按学号和姓名搜索
+ if params[:search]
+ @users_reviews = @users_reviews.where("concat(lastname, firstname) like ? or student_id like ?", params[:search], params[:search])
+ end
+ # 抄袭作品数
+ @copy_reviews_count = @users_reviews.count
+ # 排序搜索
+ @users_reviews = @users_reviews.order("#{rorder} #{sort}").page(page).per(limit)
+ # 获取所有查重过的分班
+ @course_groups = @course.course_groups.where(id: @homework.homework_group_reviews.pluck(:course_group_id).uniq)
+
+ # 如果未分班被查重过,则显示未分班列
+ @non_course_group =
+ if @homework.homework_group_reviews.where(course_group_id: 0).count > 0
+ @course.course_members.where(role: 4, course_group_id: 0).count
+ end
+
+ # 最新一次的查重时间
+ @last_review_time = format_time @homework.homework_group_reviews.last.try(:created_at)
+
+ end
+
+ # 代码查重详情
+ def code_review_detail
+ @student_work = @homework.student_works.find_by(user_id: params[:user_id])
+ @user = @student_work.user
+ tip_exception("当前用户无作品可以显示") if @student_work.nil?
+ # 查询最新一次的查重标识query_id
+ group_id = @course.course_members.where(user_id: params[:user_id]).pluck(:course_group_id).first
+ query_id = @homework.homework_group_reviews.where(:course_group_id => group_id).last.try(:query_id)
+ results = ReviewService.query_result({user_id: params[:user_id], query_id: query_id})
+ @shixun = @homework.shixuns.take
+ if results.status == 0
+ code_info = results.code_info
+ homework_challenge_settings = @homework.homework_challenge_settings
+ @challenges = @shixun.challenges.where(id: homework_challenge_settings.pluck(:challenge_id), st: 0).includes(:games)
+ @challenges =
+ @challenges.map do |challenge|
+ code_rate = 0
+ game_codes = results.code_info.select {|info| challenge.path.split(";").include?(info.origin_path)}
+ # 先判断用户该关卡是否查重了 取多个待补充文件的平均值
+ if game_codes.count > 0
+ code_rate += game_codes.map(&:rate).sum / challenge.path.split(";").length
+ end
+ target = game_codes.count > 0 ? game_codes[0].target_user_id : nil
+ # 作品完成时间
+ game = challenge.games.find_by(user_id: @user.id)
+ end_time = game.end_time
+ # 用户关卡的得分
+ all_score = homework_challenge_settings.find_by(challenge_id: challenge.id).try(:score)
+ final_score =
+ if @student_work.challenge_work_scores.where(challenge_id: challenge.id).last.present?
+ @student_work.challenge_work_scores.where(:challenge_id => game.challenge_id).last.score
+ else
+ if game.status == 2 && ((game.end_time && game.end_time < @homework.end_time) ||
+ (@homework.allow_late && (@course.end_date.nil? ||
+ (game.end_time && game.end_time < @course.end_date.end_of_day))))
+ answer_open_evaluation = @homework.homework_detail_manual.answer_open_evaluation
+ # 设置了查看答案也获得满分的话就取总分。否则取关卡的百分比分支
+ if answer_open_evaluation.present?
+ all_score
+ else
+ # 关卡的百分比 * 作业设置的分数 = 总得分
+ ((game.final_score) / challenge.score) * all_score
+ end
+ end
+ end
+ # 抄袭用户
+ copy_user = User.find_by_id(game_codes[0].target_user_id)
+ copy_end_time = copy_user.games.find_by(challenge_id: challenge.id).try(:end_time) if copy_user.present?
+ # 代码部分
+ code_list = []
+ challenge.path.split(";").each do |path|
+ if code_info.select{|info| path == info.origin_path}.size > 0
+ info = code_info.select{|info| path == info.origin_path}[0]
+ code_list << {path: path, origin_content: info.origin_content, target_content: info.target_content}
+ end
+ end
+
+ {code_rate: code_rate, copy_user_id: copy_user.try(:id), end_time: end_time, final_score: final_score,
+ all_score: all_score, copy_end_time: copy_end_time, copy_username: copy_user.try(:full_name),
+ username: game.user.full_name, code_list: code_list, subject: challenge.subject, position: challenge.position,
+ id: challenge.id}
+ end
+
+ else
+ if results.status == 1
+ tip_exception(-1, "代码查重异常,请稍后重试")
+ else
+ tip_exception(-2, "代码查重正在执行中,请稍后")
+
+ end
+ end
+
+ end
+
+ private
+
+ def find_homework
+ begin
+ @homework = HomeworkCommon.find(params[:id])
+ @course = @homework.course
+ @homework_detail_manual = @homework.homework_detail_manual
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("id不存在")
+ end
+ end
+
+ def homework_params
+ tip_exception("name参数不能为空") if params[:name].blank?
+ tip_exception("description参数不能为空") if params[:description].blank?
+ params.require(:homework_common).permit(:name, :description, :reference_answer)
+ end
+
+ def require_id_params
+ tip_exception("请至少选择一个作业") if params[:homework_ids].blank?
+ tip_exception("批量设置不能超过15个") if params[:homework_ids].length > 15
+ end
+
+ def validate_min_max_num
+ tip_exception("min_num参数不能为空") if params[:min_num].blank?
+ tip_exception("max_num参数不能为空") if params[:max_num].blank?
+ tip_exception("最小人数不能小于1") if params[:min_num].to_i < 1
+ tip_exception("最大人数不能小于最小人数") if params[:max_num].to_i < params[:min_num].to_i
+ end
+
+ def validate_absence_penalty
+
+ end
+
+ def create_homework_group_settings homework
+ if homework.homework_group_settings.size != @course.course_groups.size
+ @course.course_groups.where.not(id: homework.homework_group_settings.pluck(:course_group_id)).each do |group|
+ homework.homework_group_settings << HomeworkGroupSetting.new(course_group_id: group.id, course_id: @course.id,
+ publish_time: homework.publish_time, end_time: homework.end_time)
+ end
+ end
+ end
+
+ def get_new_code_reviews_result homework
+ if homework.code_reviews_new_results?
+ # 获取最新的查询id
+ query_id = homework.homework_group_reviews.where(status: 0).last.try(:query_id)
+ results = ReviewService.query_result({query_id: query_id})
+ if results.status == 0
+ shixun = homework.shixuns.take
+ challenges = shixun.challenges.where(id: homework.homework_challenge_settings.pluck(:challenge_id), st: 0)
+ challenge_count = challenges.count
+ Rails.logger.info("#####results_user_list: #{results.user_lists.to_json}")
+ results.user_lists.map(&:user_id).uniq.each do |user|
+ user_rate = 0
+ # 计算每个关卡的相似度
+ challenges.each do |challenge|
+ game_codes = results.user_lists.select{|user_list| user_list.user_id == user &&
+ challenge.path.split(";").include?(user_list.origin_path)}
+ # 先判断用户该关卡是否查重了 取多个待补充文件的平均值
+ if game_codes.count > 0
+ user_rate += game_codes.map(&:rate).sum / challenge.path.split(";").length
+ end
+ end
+ user_rate = challenge_count == 0 ? 0 : user_rate / challenge_count
+
+ # 如果用户已有查重记录则更新相似度 否则新建一条记录
+ user_review = homework.homework_review_results.find_by(:user_id => user)
+ if user_review.present?
+ user_review.update_attributes(:code_rate => user_rate)
+ else
+ homework.homework_review_results.create(:user_id => user, :code_rate => user_rate)
+ end
+ end
+ nuser_ids = results.user_lists.map(&:user_id).uniq
+ homework.homework_review_results.where.not(user_id: nuser_ids).destroy_all
+ homework.homework_group_reviews.where(status: 0).update_all(status: 1)
+ elsif results.status == 2
+ tip_exception(-2, "代码查重正在执行中,请稍后")
+ else
+ tip_exception(-1, "代码查重异常,请稍后重试")
+ end
+ end
+ end
+
+ def add_to_homework_bank_f homework
+ homework_bank = HomeworkBank.new(name: homework.name, description: homework.description, user_id: current_user.id,
+ homework_type: homework.homework_type == "normal" ? 1 : 3, quotes: 1, is_public: 0,
+ homework_common_id: homework.id, reference_answer: homework.reference_answer,
+ course_list_id: @course.course_list_id)
+ if homework.homework_type == "group" && homework.homework_detail_group
+ homework_bank.min_num = homework.homework_detail_group.min_num
+ homework_bank.max_num = homework.homework_detail_group.max_num
+ homework_bank.base_on_project = homework.homework_detail_group.base_on_project
+ end
+ homework.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = homework_bank.user_id
+ att.copy_from = attachment.id
+ homework_bank.attachments << att
+ end
+ homework_bank
+ end
+
+end
diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb
new file mode 100644
index 000000000..0e2628c3e
--- /dev/null
+++ b/app/controllers/main_controller.rb
@@ -0,0 +1,5 @@
+class MainController < ApplicationController
+ def index
+ render file: 'public/react/build/index.html', :layout => false
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/memos_controller.rb b/app/controllers/memos_controller.rb
new file mode 100644
index 000000000..723f2e9a4
--- /dev/null
+++ b/app/controllers/memos_controller.rb
@@ -0,0 +1,138 @@
+class MemosController < ApplicationController
+ before_action :set_memo, only: [:show, :edit, :update, :destroy]
+
+ include ApplicationHelper
+ # GET /memos
+ # GET /memos.json
+ def index
+ @user = current_user
+ @memos = Memo.all
+ s_order = (params[:order] == "replies_count" ? "all_replies_count" : params[:order]) || "updated_at"
+ #@tidding_count = unviewed_tiddings(current_user) if current_user.present?
+ page = params[:page].to_i
+ search = params[:search]
+ offset = page * 15
+ forum_id = params[:forum]
+ user_id = params[:user_id]
+ if user_id == -1
+ user_id = current_user.try(:id)
+ end
+ tag_repertoire_id = params[:tag_repertoire_id]
+
+ sql =
+ if forum_id
+ search ? "forum_id = #{forum_id} and root_id is null and subject like '%#{search}%'" :
+ "forum_id = #{forum_id} and root_id is null"
+ elsif search
+ user_id ? "author_id = #{user_id.to_i} and forum_id in(3, 5) and root_id is null and subject like '%#{search}%'" :
+ "forum_id in(3, 5) and root_id is null and subject like '%#{search}%'"
+ else
+ user_id ? "author_id = #{user_id.to_i} and forum_id in(3, 5) and root_id is null" :
+ "forum_id in(3, 5) and root_id is null"
+ end
+
+ if tag_repertoire_id
+ memo_ids = MemoTagRepertoire.where(tag_repertoire_id: tag_repertoire_id).pluck(:memo_id)
+ memo_ids = memo_ids ? memo_ids.join(",") : -1
+ sql += " and #{Memo.table_name}.id in(#{memo_ids})"
+ end
+
+ if params[:order] == "updated_at"
+ sql += " and all_replies_count != 0"
+ end
+
+ memos = Memo.field_for_list.includes(:praise_tread, :author).where("#{sql}")
+ @memos_count = memos.length
+ @memos = memos.order("sticky = 1 desc, #{Memo.table_name}.#{s_order} desc").offset(offset).limit(15)
+ @my_memos_count = Memo.user_posts(current_user.try(:id)).count
+ @tags_info = MemoTagRepertoire.find_by_sql("SELECT tag_repertoire_id, tr.name, count(*) cnt
+ FROM memo_tag_repertoires mtr join tag_repertoires tr on
+ tr.id = mtr.tag_repertoire_id group by tag_repertoire_id order by cnt desc,
+ tag_repertoire_id desc limit 9")
+ @hot_memos = Memo.field_for_recommend.posts.hot.limit(4)
+ end
+
+ # GET /memos/1
+ # GET /memos/1.json
+ def show
+ # tidding_count = unviewed_tiddings(current_user) if current_user
+ @user = current_user
+ # TODO 附件最后再做
+ # attachments_list =
+ @memo.update_column(:viewed_count, @memo.viewed_count+1)
+ @memos = @memo.reply_for_memo.includes(:praise_tread, :author).order("created_at desc").limit(10)
+
+ end
+
+ # GET /memos/new
+ def new
+ @csrf_token = session[:_csrf_toke] ||= SecureRandom.base64(32)
+ @tag_list = TagRepertoire.field_for_list.order("name asc")
+
+ end
+
+ # GET /memos/1/edit
+ def edit
+ end
+
+ # POST /memos
+ # POST /memos.json
+ def create
+ ActiveRecord::Base.transaction do
+ begin
+ @memo = Memo.new(memo_params)
+ @memo.author = current_user
+ # TODO 保存附件
+ # @memo.save_attachments(params[:attachments]) if params[:attachments]
+ @memo.save!
+ params[:tags].each do |tag|
+ MemoTagRepertoire.create(:memo_id => @memo.id, :tag_repertoire_id => tag)
+ end
+ @status = 0
+ @message = "帖子创建成功!"
+ rescue Exception => e
+ @status = -1
+ @message = "帖子创建失败,原因:#{e}"
+ raise ActiveRecord::Rollback
+ end
+ end
+
+
+ end
+
+ # PATCH/PUT /memos/1
+ # PATCH/PUT /memos/1.json
+ def update
+ respond_to do |format|
+ if @memo.update(memo_params)
+ format.html { redirect_to @memo, notice: 'Memo was successfully updated.' }
+ format.json { render :show, status: :ok, location: @memo }
+ else
+ format.html { render :edit }
+ format.json { render json: @memo.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /memos/1
+ # DELETE /memos/1.json
+ def destroy
+ @memo.destroy
+ respond_to do |format|
+ format.html { redirect_to memos_url, notice: 'Memo was successfully destroyed.' }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_memo
+ @memo = Memo.find(params[:id])
+ end
+
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def memo_params
+ params.fetch(:memo, {})
+ end
+
+end
diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb
new file mode 100644
index 000000000..8e340da73
--- /dev/null
+++ b/app/controllers/messages_controller.rb
@@ -0,0 +1,206 @@
+class MessagesController < ApplicationController
+ include MessagesHelper
+
+ SORT_TYPE = %w[time hot]
+
+ before_action :require_login, only: %i[create update sticky_top bulk_delete create destroy bulk_send bulk_move bulk_public]
+ before_action :find_board, only: [:create, :index, :bulk_delete, :bulk_move, :bulk_send, :bulk_public]
+ before_action :find_message, only: [:update, :destroy, :sticky_top, :reply_list, :destroy, :reply]
+ before_action :validate_delete_params, only: %i[bulk_delete bulk_public]
+ before_action :message_validate_create_params, only: :create
+ before_action :validate_update_params, only: :update
+ before_action :validate_sort_type, only: :index
+ before_action :validate_send_message_to_course_params, only: :bulk_send
+ before_action :validate_move_params, only: :bulk_move
+
+ def index
+ @page = params[:page] || 1
+ @page_size = params[:page_size] || 15
+
+ sort = params[:sort] || 0
+ sort_type = params[:sort_type] || 'time'
+ sort = sort.to_i
+ sort_type = sort_type.strip
+
+ @messages = @board.messages.root_nodes.by_keywords(params[:search]).includes(:praise_treads, :author, :children)
+ @messages = @messages.ordered(sort: sort, sort_type: sort_type)
+ @messages = sort_by_all_replies(sort, sort_type, @messages)
+
+ @messages = sort_by_sticky(@messages)
+ @messages = Kaminari.paginate_array(@messages).page(@page).per(@page_size)
+ end
+
+ def reply_list
+ @page = params[:page] || 1
+ @page_size = params[:page_size] || 10
+ @current_user = current_user || nil
+
+ @messages = @message.children.preload_messages
+ @messages = @messages.ordered(sort: 1) unless @message.parent_id.nil?
+
+ @messages = @messages.page(@page).per(@page_size)
+ end
+
+ def reply
+ return normal_status(2, "回复内容不能为空") if params[:content].blank?
+ begin
+ @reply = Message.create!(board: @message.board,
+ author: current_user,
+ parent: @message,
+ message_detail_attributes: {
+ content: params[:content]
+ }
+ )
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def sticky_top
+ return normal_status(403, "您没有权限进行该操作") unless current_user.teacher_of_course?(@message.board.course)
+
+ ActiveRecord::Base.transaction do
+ begin
+ @message.update_attributes(:sticky => @message.sticky == 1 ? 0 : 1)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def bulk_delete
+ ActiveRecord::Base.transaction do
+ begin
+ @messages = @board.messages.by_ids(params[:ids])
+ @messages.destroy_all
+ rescue Exception => e
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def new
+ @message = Message.new
+ end
+
+ def show
+ @message = Message.includes(:attachments, :message_detail, :children, :author => :user_extension, :board => [{course: :board_course_modules}]).find_by_id params[:id]
+ return normal_status(-2, "ID为#{params[:id]}的帖子不存在") if @message.nil?
+
+ @attachment_size = @message.attachments.size
+ @message.update_visits
+ @current_user = current_user
+ end
+
+ def update
+ return normal_status(403, "您没有权限进行该操作") if current_user != @message.author && !current_user.teacher_of_course?(@message.board.course)
+
+ begin
+ @message.update_attributes(message_params)
+ Attachment.associate_container(params[:attachment_ids], @message.id, @message.class.name)
+ @message.update_content(params[:content])
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("修改失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def create
+ return normal_status(403, "您没有权限进行该操作") unless current_user.admin? || current_user.member_of_course?(@board.course)
+
+ begin
+ @message = Message.new(message_params)
+ @message.author = current_user
+ @message.board_id = params[:select_board_id]
+ @message.message_detail_attributes = {content: params[:content]}
+ @message.save!
+ Attachment.associate_container(params[:attachment_ids], @message.id, @message.class.name)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def destroy
+ begin
+ return normal_status(403, "您没有权限进行该操作") unless @message.author == current_user || current_user.teacher_of_course?(@message.board.course)
+ @message.destroy!
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def bulk_send
+ return normal_status(403) unless current_user.teacher_or_admin?(@board.course)
+ ids = params[:ids]
+ course_ids = params[:to_course_ids]
+
+ begin
+ ids.each do |id|
+ @message = Message.find_by_id id
+ if @message.try(:parent_id).nil? # TODO 暂时只支持目录下的跟节点发送
+ course_ids.each do |course_id|
+ course = Course.find course_id
+ new_message = Message.create!(board: course.course_board,
+ subject: @message.subject,
+ author: current_user,
+ message_detail_attributes: {
+ content: @message.try(:message_detail).try(:content)
+ }
+ )
+ @message.copy_attachments_to_new_message(new_message, current_user)
+ end
+ end
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def bulk_move
+ # 课堂的目录之间移动,有子栏目的才显示此项
+ return normal_status(403) unless current_user.teacher_of_course?(@board.course)
+
+ begin
+ Message.bulk_move_to_other_board(params[:ids], params[:to_board_id], current_user.id)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def bulk_public
+ @messages = @board.messages.root_nodes.by_ids Array(params[:ids])
+ @messages.update_all(is_public: true)
+ end
+
+ private
+ def validate_sort_type
+ normal_status(2, "参数sort_tyope暂时只支持 'time', 'hot'两种") if params.has_key?(:sort_type) && !SORT_TYPE.include?(params[:sort_type].strip)
+ end
+
+ def find_message
+ begin
+ @message = Message.find params[:id]
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ end
+ end
+
+ def message_params
+ params.require(:message).permit(:subject, :sticky)
+ end
+end
diff --git a/app/controllers/myshixuns_controller.rb b/app/controllers/myshixuns_controller.rb
new file mode 100644
index 000000000..042945b6e
--- /dev/null
+++ b/app/controllers/myshixuns_controller.rb
@@ -0,0 +1,362 @@
+class MyshixunsController < ApplicationController
+ before_action :require_login, :except => [:training_task_status, :code_runinng_message]
+ before_action :find_myshixun, :except => [:training_task_status]
+ before_action :find_repo_name, :except => [:training_task_status]
+ skip_before_action :verify_authenticity_token, :only => [:html_content]
+
+ ## TPI关卡列表
+ def challenges
+ # @challenges = Challenge.where(shixun_id: params[:shixun_id])
+
+ @shixun_status = @myshixun.shixun.status
+ @games = @myshixun.games.includes(:challenge).reorder("challenges.position")
+ end
+
+
+ # For Admin
+ # 强制重置实训
+ # REDO等删除是否可以做成异步
+ # 前段需要按照操作过程提示
+ def reset_my_game
+ unless (current_user.admin? || current_user.id == @myshixun.user_id)
+ tip_exception("403", "")
+ end
+
+ ActiveRecord::Base.transaction do
+ begin
+ @shixun = Shixun.select(:id, :identifier).find(@myshixun.shixun_id)
+ @myshixun.destroy
+
+ # 刪除版本庫
+ begin
+ GitService.delete_repository(repo_path: @repo_path)
+ rescue Exception => e
+ uid_logger_error("版本库删除异常,详情:#{e.message}")
+ end
+
+ StudentWork.where(:myshixun_id => @myshixun.id).update_all(:myshixun_id => nil, :work_status => 0)
+
+ rescue Exception => e
+ uid_logger_error("myshixun reset failed #{e}")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 代码运行中的信息接口
+ def code_runinng_message
+ begin
+ jsonTestDetails = JSON.parse(params[:jsonTestDetails])
+ game_id = jsonTestDetails['buildID']
+ message = jsonTestDetails['textMsg']
+ if game_id.present? && message.present?
+ game = Game.find game_id
+ msg = game.run_code_message
+ # 只有评测中的game才会创建和更新代码评测中的信息
+ if game.status == 1 || game.status == 2
+ if msg.blank?
+ RunCodeMessage.create!(:game_id => game_id, :status => 1, :message => message)
+ else
+ msg.update_attributes(:status => (msg.status + 1), :message => message)
+ end
+ end
+ render :json => {:data => "success"}
+ end
+ rescue Exception => e
+ render :json => {:data => "failed, exception_message: #{e}"}
+ end
+ end
+
+ # 中间层评测接口
+ # taskId 即返回的game id
+ # 返回结果:params [:stauts] 0 表示成功,其它则失败
+ # msg 错误信息
+ # output 为测试用户编译输出结果
+ # myshixun:status 1为完成实训
+ # @jenkins: caseId对应test_set的position,passed: 1表示成功,0表示失败
+ # resubmit 1:表示已通关后重新评测;0:表示非重新评测
+ # retry_status 0:初始值;1:重新评测失败;2:重新评测成功
+ # tpiRepoPath 中间层图片的workspace路径
+ # params[:jsonTestDetails] = '{"buildID":"19284","compileSuccess":"1",
+ # "msg":[{"caseId":"1","expectedOutput":"MSAyIDMNCg","input":"MiAzIDE","output":"MSAyIDMNCg","passed":"1"},
+ # {"caseId":"2","expectedOutput":"LTMgMSA2DQo","input":"LTMgNiAx","output":"LTMgMSA2DQo","passed":"1"},
+ # {"caseId":"3","expectedOutput":"LTcgLTUgLTMNCg","input":"LTcgLTMgLTU","output":"LTcgLTUgLTMNCg","passed":"1"}],
+ # "outPut":"Y29tcGlsZSBzdWNjZXNzZnVsbHk","resubmit":"","status":"0"}'
+ # params[:timeCost] = '{"evaluateEnd":"2017-11-24 11:04:37","pull":"0.086",
+ # "createPod":"1.610","evaluateAllTime":2820,"evaluateStart":"2017-11-24 11:04:35","execute":"0.294"}'
+ # params[:pics] = "a.png,b.png,c.png"
+ def training_task_status
+ logger.info("123################{params[:jsonTestDetails]}")
+ logger.info("456################{params[:timeCost]}")
+ logger.info("666###############{params}")
+
+ ActiveRecord::Base.transaction do
+ begin
+ t1 = Time.now
+ jsonTestDetails = JSON.parse(params[:jsonTestDetails])
+ timeCost = JSON.parse(params[:timeCost])
+ brige_end_time = Time.parse(timeCost['evaluateEnd']) if timeCost['evaluateEnd'].present?
+ return_back_time = format("%.3f", ( t1.to_f - brige_end_time.to_f)).to_f
+ status = jsonTestDetails['status']
+ game_id = jsonTestDetails['buildID']
+ logger.info("training_task_status start#1**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
+ resubmit = jsonTestDetails['resubmit']
+ outPut = tran_base64_decode64(jsonTestDetails['outPut'])
+ jenkins_testsets = jsonTestDetails['msg']
+ compile_success = jsonTestDetails['compileSuccess']
+ # message = Base64.decode64(params[:msg]) unless params[:msg].blank?
+ logger.info(outPut)
+ game = Game.find(game_id)
+ myshixun = game.myshixun
+ challenge = game.challenge
+ # test_sets = challenge.test_sets
+ if challenge.picture_path.present?
+ #pics = params[:files]
+ pics = params[:tpiRepoPath]
+ game.update_column(:picture_path, pics)
+ end
+ logger.info("training_task_status start#2**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
+ max_query_index = game.outputs ? (game.outputs.first.try(:query_index).to_i + 1) : 1
+ test_set_score = 0
+ unless jenkins_testsets.blank?
+ jenkins_testsets.each_with_index do |j_test_set, i|
+ logger.info("j_test_set: ############## #{j_test_set}")
+ actual_output = tran_base64_decode64(j_test_set['output'])
+ # is_public = test_sets.where(:position => j_test_set['caseId']).first.try(:is_public)
+ logger.info "actual_output:################################################# #{actual_output}"
+ Output.create!(:code => status, :game_id => game_id, :out_put => outPut, :test_set_position => j_test_set['caseId'],
+ :actual_output => actual_output, :result => j_test_set['passed'].to_i, :query_index => max_query_index,
+ :compile_success => compile_success.to_i)
+ # 如果设置了按测试集给分,则需要统计测试集的分值
+ if challenge.test_set_score && j_test_set['passed'].to_i == 1
+ test_set_score += challenge.test_sets.where(:position => j_test_set['caseId']).pluck(:score).first
+ end
+ end
+ end
+ uid_logger("#############status: #{status}")
+ uid_logger("#############resubmit: #{resubmit}")
+ record = EvaluateRecord.where(:game_id => game_id).first
+ logger.info("training_task_status start#3**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
+ answer_deduction_percentage = (100 - game.answer_deduction) / 100.to_f # 查看答案后剩余分数的百分比.
+ # answer_deduction是查看答案的扣分比例
+ # status:0表示评测成功
+ if status == "0"
+ if resubmit.present?
+ game.update_attributes!(:retry_status => 2, :resubmit_identifier => resubmit)
+ challenge.path.split(";").each do |path|
+ game_passed_code(path.try(:strip), myshixun, game_id)
+ end
+ else
+ game.update_attributes!(:status => 2,
+ :end_time => Time.now,
+ :accuracy => format("%.4f", 1.0 / game.query_index))
+ myshixun.update_attributes!(:status => 1) if game.had_done == 1
+ challenge.path.split(";").each do |path|
+ game_passed_code(path.try(:strip), myshixun, game_id)
+ end
+ # 如果是已经发布的实训,则需要给出相应的奖励
+ if challenge.shixun.try(:status) > 1
+ score = (challenge.score * answer_deduction_percentage).to_i
+ if score > 0
+ reward_attrs = { container_id: game.id, container_type: 'Game', score: score }
+ RewardGradeService.call(game.user, reward_attrs)
+ RewardExperienceService.call(game.user, reward_attrs)
+ end
+ # 需要扣除查看答案的分数
+ game.update_attributes!(:final_score => score)
+ end
+
+ # 更新实训关联的作品分数 TODO: 更新作品分数
+ HomeworksService.new.update_myshixun_work_score myshixun
+ end
+ # 如果过关了,下一关的状态是3(为开启),则需要把状态改成1(已开启)
+ # next_game = game.next_game
+ next_game = game.next_game(myshixun.shixun_id, game.myshixun_id, challenge.position)
+ next_game.update_column(:status, 0) if next_game.present? && next_game.status == 3
+ # status == "-1" 表示返回结果错误
+ else
+ if resubmit.present?
+ game.update_attributes!(:retry_status => 1, :resubmit_identifier => resubmit)
+ else
+ # 评测没通关则,测试集对的个数给分,并且还要扣除用户是否查看答案的值
+ test_set_percentage = test_set_score / 100.to_f # 测试集得分比
+ score = (challenge.score * test_set_percentage * answer_deduction_percentage).to_i
+ # 如果分数比上次多,则更新成绩
+ game.update_attributes!(:status => 0, :final_score => score) if game.final_score < score
+ end
+ end
+ test_cases_time = format("%.3f", (Time.now.to_f - t1.to_f)).to_f
+ if record.present?
+ consume_time = format("%.3f", (Time.now - record.created_at)).to_f
+ record.update_attributes!(:consume_time => consume_time, :git_pull => timeCost['pull'] ,
+ :create_pod => timeCost['createPod'], :pod_execute => timeCost['execute'], :test_cases => test_cases_time,
+ :brige => timeCost['evaluateAllTime'], :return_back => return_back_time)
+ end
+ uid_logger("training_task_status start#4**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
+ sucess_status
+ rescue Exception => e
+ tip_exception(e.message)
+ uid_logger_error("training_task_status error: #{e}")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 连接webssh
+ def open_webssh
+ username = edu_setting('webssh_username')
+ password = edu_setting('webssh_password')
+ old_time = Time.now.to_i
+ begin
+ shixun_tomcat = edu_setting('tomcat_webssh')
+ uri = "#{shixun_tomcat}/bridge/webssh/getConnectInfo"
+ params = {tpiID:@myshixun.id, podType:@myshixun.shixun.try(:webssh),
+ containers:(Base64.urlsafe_encode64(container_limit @myshixun.shixun.mirror_repositories))}
+ res = uri_post uri, params
+ if res && res['code'].to_i != 0
+ tip_exception("实训云平台繁忙(繁忙等级:92)")
+ end
+ render :json => {:host => res['address'],
+ :port => res['port'],
+ :ws_url => res['ws_address'],
+ :username => username,
+ :password => password,
+ :game_id => @myshixun.id,
+ :webssh_url => "#{shixun_tomcat}/bridge"}
+ rescue Exception => e
+ logger.error(e)
+ render :json => {:error => e.try(:message)}
+ ensure
+ use_time = Time.now.to_i - old_time
+ logger.info "open_webssh tpiID #{@myshixun.id} use time #{use_time}"
+ end
+ end
+
+ include GitCommon
+
+ # -----Repository
+ # TODO: 之类需要一个resubmit参数,但是是关于games.
+ def update_file
+ @hide_code = Shixun.where(id: @myshixun.shixun_id).pluck(:hide_code).first
+ tip_exception("技术平台为空!") if @myshixun.mirror_name.blank?
+ path = params[:path].strip unless params[:path].blank?
+ game_id = params[:game_id]
+ game = Game.find(game_id)
+ @content_modified = 0
+ # params[:evaluate] 实训评测时更新必须给的参数,需要依据该参数做性能统计,其它类型的更新可以跳过
+ # 自动保存的时候evaluate为0;点评测的时候为1
+ if params[:evaluate] == 1
+ record = EvaluateRecord.create!(:user_id => current_user.id, :shixun_id => @myshixun.shixun_id, :game_id => game_id)
+ uid_logger("-- game is #{game_id}, record id is #{record.id}, time is **** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
+ student_work_time = format("%.3f", (Time.now.to_f - record.created_at.to_f)).to_f
+ record.update_attributes!(:student_work => student_work_time)
+ end
+ unless @hide_code
+ # 远程版本库文件内容
+ last_content = GitService.file_content(repo_path: @repo_path, path: path)["content"]
+
+ content = if @myshixun.mirror_name.select{|a| a.include?("MachineLearning") || a.include?("Python")}.present? && params[:content].present?
+ params[:content].gsub(/\t/, ' ')
+ else
+ params[:content]
+ end
+ if content != last_content
+ @content_modified = 1
+
+ author_name = current_user.full_name
+ author_email = current_user.mail
+ message = params[:evaluate] == 0 ? "auto commit" : "task commit"
+ @content = GitService.update_file(repo_path: @repo_path,
+ file_path: path,
+ message: message,
+ content: content,
+ author_name: author_name,
+ author_email: author_email)
+
+ uid_logger("-- file update #{@content}")
+ end
+ end
+
+
+ if game.status == 2
+ @resubmit = Time.now.to_i
+ end
+
+ # 评测时间记录
+ if record.present?
+ consume_time = format("%.3f", (Time.now.to_f - record.created_at.to_f)).to_f
+ record.update_attributes!(:file_update => consume_time)
+ end
+ end
+
+ # 渲染实训代码
+ # educodercss: 字符串以 ‘,’分隔,存储的是版本库css的路径
+ # educoderscript: 字符串以 ‘,’分隔,存储的是版本库js的路径
+ # contents: html实训的整体内容
+ def html_content
+ @contents = params[:contents] || ""
+ edu_css = params[:educodercss]
+ edu_js = params[:educoderscript]
+ if @contents.present?
+ @contents = @contents.gsub("w3equalsign", "=").gsub("w3scrw3ipttag", "script").gsub("edulink", "link").html_safe
+ end
+ # css
+ if edu_css.present?
+ css_path = edu_css.split(",")
+ css_path.each do |path|
+ file_content = GitService.file_content(repo_path: @repo_path, path: path)["content"]
+ file_content = tran_base64_decode64(file_content) unless file_content.blank?
+ @contents = @contents.sub(/EDUCODERCSS/, "")
+ end
+ end
+ # js
+ if edu_js.present?
+ js_path = edu_js.split(",")
+ js_path.each do |path|
+ file_content = GitService.file_content(repo_path: @repo_path, path: path)["content"]
+ file_content = tran_base64_decode64(file_content) unless file_content.blank?
+ @contents = @contents.sub(/EDUCODERJS/, "")
+ end
+ end
+ respond_to do |format|
+ format.json
+ format.html{render :layout => false}
+ end
+ end
+
+ # 最新可以用的并发测试接口
+ def sigle_mul_test
+ codes = %W(1 2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z)
+ begin
+ identifiers = Myshixun.where(:shixun_id => params[:shixun_id].split(",")).pluck(:identifier)
+ ide = identifiers[rand(identifiers.length)]
+ myshixun = Myshixun.where(:identifier => ide).first
+
+ game = myshixun.games.last
+ logger.warn("###2mul test game_build start ")
+ identifier = game.try(:identifier)
+ if game.status == 2
+ code = codes.sample(8).join
+ resubmit = "#{code}_#{myshixun.id}"
+ end
+ logger.warn("###3mul test game_build start ...")
+ EvaluateRecord.create!(:user_id => myshixun.user_id, :shixun_id => myshixun.shixun.id, :game_id => game.id)
+ redirect_to "/api/games/#{identifier}/game_build?resubmit=#{resubmit}&content_modified=0&first=1"
+ rescue Exception => e
+ logger.error("mul test failed ===> #{e.message}")
+ end
+ end
+
+
+ # -----End
+
+ private
+ def find_myshixun
+ @myshixun = Myshixun.find_by!(identifier: params[:identifier])
+ end
+
+ def find_repo_name
+ @repo_path = @myshixun.try(:repo_path)
+ @path = params[:path]
+ end
+end
diff --git a/app/controllers/oauth_controller.rb b/app/controllers/oauth_controller.rb
new file mode 100644
index 000000000..ff5908cd0
--- /dev/null
+++ b/app/controllers/oauth_controller.rb
@@ -0,0 +1,54 @@
+class OauthController < ApplicationController
+ DEFAULT_PASSWORD = "a12345678"
+ TOKEN_CALL_BACK = "/oauth/get_token_callback"
+ USER_INFO = "/oauth/userinfo"
+
+ def get_code
+ identity_site = edu_setting('openi_domain')
+ root_url = edu_setting('educoder_domain')
+
+ # 从OpenI发过来的回调中获取授权码
+ code = params[:code]
+
+ # 利用授权码从OpenI这里获取access_token
+ client = get_client(identity_site)
+ redirect_uri = "#{root_url}#{TOKEN_CALL_BACK}"
+ access_token_hash = client.auth_code.get_token(code, redirect_uri: redirect_uri).to_hash
+
+ # 利用access_token获取OpenI的用户信息
+ access_token = access_token_hash[:access_token]
+ get_info_url = "#{identity_site}#{USER_INFO}?access_token=#{access_token}"
+ response = HTTParty.get(get_info_url)
+ body_json = JSON.parse response.body
+
+ openi_user_id = body_json['token']
+ avatar_url = body_json['avatar_url']
+ login = body_json['login']
+ name = body_json['name']
+ email = body_json['email']
+
+ # 根据获取的用户信息来查询数据库,如已经存在对应的Educoder用户,则直接访问用户要访问的实训页面,否则为其创建用户后再访问实训页面
+ openi = Openi.find_by_login(login)
+ unless openi
+ ActiveRecord::Base.transaction do
+ user = User.new(lastname: name, mail: email, mail_notification: email)
+ user.login = login
+ user.password = DEFAULT_PASSWORD
+ user.save!
+
+ UserExtensions.create!(user_id: user.id, school_id: School.first.id, identity: 4, gender: 0)
+
+ UserDayCertification.create!(user_id: user.id, status: 1)
+
+ openi = Openi.create!(user_id: user.id, openi_user_id: openi_user_id, avatar_url: avatar_url, login: login, name: name, email: email)
+ end
+ end
+
+ self.logged_user = openi.user
+ original_url = params[:original_url]
+ redirect_to original_url
+ end
+
+ def get_token_callback
+ end
+end
diff --git a/app/controllers/poll_questions_controller.rb b/app/controllers/poll_questions_controller.rb
new file mode 100644
index 000000000..69aef41c0
--- /dev/null
+++ b/app/controllers/poll_questions_controller.rb
@@ -0,0 +1,314 @@
+class PollQuestionsController < ApplicationController
+ before_action :require_login
+ before_action :get_poll,only:[:new,:create]
+ before_action :get_poll_question,except: [:new,:create]
+ before_action :is_course_teacher
+ before_action :get_poll_questions_count,only:[:create]
+ before_action :get_poll_question_answers,only:[:edit,:update]
+ before_action :check_poll_status,only: [:new,:create,:delete_answer,:destroy]
+ before_action :validates_params,only:[:create,:update]
+ before_action :validates_update_params,only: [:update]
+
+
+ def new
+ ActiveRecord::Base.transaction do
+ begin
+ @poll_question = @poll.poll_questions.new
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 创建题目和选择的答案
+ def create
+ ActiveRecord::Base.transaction do
+ begin
+ poll_options = {
+ :question_title => params[:question_title],
+ :question_type => params[:question_type],
+ :is_necessary => params[:is_necessary].to_i,
+ :question_number => @poll_ques_count + 1,
+ :max_choices => params[:max_choices] || nil,
+ :min_choices => params[:min_choices] || nil
+ }
+ @poll_question = @poll.poll_questions.new(poll_options)
+
+ if params[:insert_id].present? #插入问题时,那么从插入的这个id以后的question_num都将要+1
+ insert_poll = @poll.poll_questions.find_by(id: params[:insert_id])
+ if insert_poll.present? #如果该问题存在的话,意思是如果是第一题,那么就不存在插入
+ ques_num = insert_poll.question_number.to_i
+ @poll_question.question_number = ques_num + 1 #更新了问题的位置
+ @poll.poll_questions.insert_question(ques_num).update_all("question_number = question_number + 1")
+ end
+ end
+ if @poll_question.save
+ if params[:question_type] != 3
+ p_answer = params[:question_answers]
+ p_other_answer = params[:question_other_answer]
+ # 新增选择题答案选择的选项
+ (1..p_answer.count).each do |i|
+ answer = p_answer[i-1] # 传入的答案的内容
+ question_option = {
+ :answer_position => i,
+ :answer_text => answer
+ }
+ poll_answers = @poll_question.poll_answers.new question_option
+ poll_answers.save
+ end
+ # 新增答案的其他选项
+ if p_other_answer
+ question_option = {
+ :answer_position => p_answer.count + 1,
+ :answer_text => ''
+ }
+ poll_answers = @poll_question.poll_answers.new question_option
+ poll_answers.save
+ end
+ end
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("问卷的问题创建失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def show
+ ActiveRecord::Base.transaction do
+ begin
+ @poll_answers = @poll_question.poll_answers
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def edit
+ ActiveRecord::Base.transaction do
+ begin
+ @poll_answers = @poll_question.poll_answers
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def update
+ ActiveRecord::Base.transaction do
+ begin
+ if @poll_question.question_type < 3 #当为单选题或多选题时
+ p_answer = params[:question_answers]
+ p_other_answer = params[:question_other_answer]
+ p_answer_count = p_answer.count
+ @poll_question.poll_answers.each do |an|
+ if (p_answer_count < @poll_current_answers) && (p_answer_count..@poll_current_answers).to_a.include?(an.answer_position)
+ an.destroy
+ end
+ end
+ (1..p_answer_count).each do |i|
+ answer = @poll_question.poll_answers.find_answer_by_custom("answer_position",i).first
+ if answer # 判断该位置的answer是否存在,存在则更新.不存在则跳到下一步
+ answer.answer_text = p_answer[i-1]
+ answer.answer_position = i
+ answer.save
+ else
+ answer_options = {
+ :answer_position => i,
+ :answer_text => p_answer[i-1]
+ }
+ @poll_question.poll_answers.new answer_options
+ end
+ end
+ if p_other_answer #判断答案的其他选项是否存在
+ other_answer = @poll_question.poll_answers.find_answer_by_custom("answer_text","").first
+ if other_answer.blank?
+ question_option = {
+ :answer_position => p_answer_count + 1,
+ :answer_text => ''
+ }
+ @poll_question.poll_answers.new question_option
+ else
+ other_answer.answer_position = p_answer_count + 1
+ other_answer.save
+ end
+ end
+ end
+
+ @poll_question.update_attributes(poll_questions_params)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("更新失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def delete_answer
+ ActiveRecord::Base.transaction do
+ begin
+ answer_d_id = params[:answer_no].to_i # 答案的当前位置
+ poll_answers = @poll_question.poll_answers
+ delete_answer = poll_answers.find_answer_by_custom("answer_position",answer_d_id).first
+ left_answer = poll_answers.left_answer_choose("answer_position",answer_d_id)
+ if left_answer.present?
+ left_answer.each do |p|
+ p.answer_position -= 1
+ p.save
+ end
+ end
+ if delete_answer.destroy
+ normal_status(0, "答案删除成功!")
+ else
+ normal_status(-1, "答案删除失败!")
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("答案删除失败!")
+ end
+ end
+ end
+
+ def destroy
+ ActiveRecord::Base.transaction do
+ begin
+ question_d_id = @poll_question.question_number.to_i #问题的当前位置
+ poll_questions = @poll.poll_questions
+ left_questions = poll_questions.insert_question(question_d_id)
+ left_questions.update_all("question_number = question_number - 1") if left_questions
+ @poll_question.destroy!
+ normal_status("删除成功")
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("问题删除失败!")
+ end
+ end
+ end
+
+ def up_down
+ ActiveRecord::Base.transaction do
+ begin
+ opr = params[:opr]
+ current_q_p = @poll_question.question_number.to_i #问题的当前位置
+ last_q_p = @poll.poll_questions.last_poll(current_q_p) #当前问题的前一个问题
+ next_q_p = @poll.poll_questions.next_poll(current_q_p) # 当前问题的后一个问题
+ if @poll.polls_status.to_i == 1
+ if opr.present?
+ if opr.to_s == "up"
+ if last_q_p.present?
+ @poll_question.update_attribute(:question_number, (current_q_p - 1))
+ last_q_p.update_attribute(:question_number, (@poll_question.question_number.to_i + 1)) # 重新获取当前问题的位置
+ normal_status(0, "问题上移成功!")
+ else
+ normal_status(-1, "移动失败,已经是第一个问题了!")
+ end
+ elsif opr.to_s == "down"
+ if next_q_p.present?
+ @poll_question.update_attribute(:question_number, (current_q_p + 1))
+ next_q_p.update_attribute(:question_number, (@poll_question.question_number.to_i - 1))
+ normal_status(0, "问题下移成功!")
+ else
+ normal_status(-1, "移动失败,已经是最后一个问题了!")
+ end
+ end
+ else
+ normal_status(-1, "移动失败,请输入参数")
+ end
+ else
+ normal_status(-1,"已发布的不能移动问题")
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("问题移动失败!")
+ end
+ end
+ end
+
+ private
+
+ def poll_questions_params
+ params.require(:poll_question).permit(:question_title,:question_type,:is_necessary,:question_number,:max_choices,:min_choices)
+ end
+
+ def validates_params
+ normal_status(-1, "问题标题不能为空!") if params[:question_title].blank?
+ normal_status(-1, "是否要求必答的值不能为空!") if params[:is_necessary].blank?
+ normal_status(-1, "问题类型不能为空!") if params[:question_type].blank?
+ if params[:min_choices].present? && params[:max_choices].present? && (params[:min_choices].to_i > params[:max_choices].to_i)
+ normal_status(-1, "最小可选不能大于最大可选!")
+ elsif params[:question_answers].present? && (params[:max_choices].to_i > params[:question_answers].count)
+ normal_status(-1, "选择题的最大可选项不能大于答案数!")
+ elsif [1,3].include?(params[:question_type]) && (params[:max_choices].to_i > 0 || params[:min_choices].to_i > 0)
+ normal_status(-1, "单选题或主观题不能有最大或最小选择数!")
+ elsif params[:question_type] == 3 && (params[:question_answers] || params[:question_other_answer])
+ normal_status(-1, "主观问题不需要可选答案!")
+ elsif params[:question_type] != 3
+ if params[:question_answers].present? && params[:question_answers].include?("")
+ normal_status(-1, "选择题不能有空值!")
+ elsif params[:question_other_answer].present? && params[:question_other_answer].length > 0
+ normal_status(-1, "其他选项不能有值!")
+ elsif params[:question_type] == 1 && params[:question_answers].count < 2
+ normal_status(-1, "单选题选项不能小于2!")
+ elsif params[:question_type] == 2 && params[:question_answers].count < 3
+ normal_status(-1, "多选题选项不能小于3!")
+ end
+ end
+ end
+
+ def validates_update_params
+ question_a_count = params[:question_answers].present? ? params[:question_answers].count : 0
+ question_o_count = params[:question_other_answer].present? ? params[:question_other_answer].count : 0
+ normal_status(-1, "已发布的问卷不允许增删答案!") if (((question_a_count+question_o_count) != @poll_current_answers) && (@poll.polls_status.to_i != 1))
+ end
+
+ def get_poll
+ @poll = Poll.find_by(id:params[:poll_id])
+ if @poll.blank?
+ tip_exception(404)
+ end
+ end
+
+ def get_poll_questions_count
+ if @poll.poll_questions.count > 0
+ @poll_ques_count = @poll.poll_questions.count
+ else
+ @poll_ques_count = 0
+ end
+ end
+
+ def get_poll_question
+ @poll_question = PollQuestion.find_by(id: params[:id])
+ if @poll_question.present?
+ @poll = Poll.find_by(id:@poll_question.poll_id)
+ else
+ tip_exception(404)
+ end
+ end
+
+ #获取问题的答案数量
+ def get_poll_question_answers
+ @poll_current_answers = @poll_question.poll_answers.count
+ end
+
+ # 已发布的问卷不能新增题目
+ def check_poll_status
+ normal_status(-1, "问卷已发布,不能更改问卷题目") if @poll.polls_status.to_i != 1
+ end
+
+ def is_course_teacher
+ @course = @poll.course
+ if @course.blank?
+ tip_exception(404)
+ else
+ @identity = current_user.course_identity(@course)
+ normal_status(-1, "权限不够") unless(@course.present? && @identity < Course::STUDENT) #课堂存在,且当前用户为教师/管理员
+ end
+ end
+end
diff --git a/app/controllers/poll_votes_controller.rb b/app/controllers/poll_votes_controller.rb
new file mode 100644
index 000000000..eef953d5e
--- /dev/null
+++ b/app/controllers/poll_votes_controller.rb
@@ -0,0 +1,157 @@
+class PollVotesController < ApplicationController
+ #在开始回答和提交问卷的时候,已经做了判断用户的身份权限
+ before_action :require_login
+ before_action :get_poll_question
+ before_action :check_answer_in_question,only: [:create]
+ before_action :check_multi_answers
+
+
+ def create #每一次答案的点击,请求一次
+ ActiveRecord::Base.transaction do
+ begin
+ question_votes = @poll_question.poll_votes
+ question_type = @poll_question.question_type
+ question_answer_id = params[:poll_answer_id] ? params[:poll_answer_id] : nil #该答案的id
+ question_answer_text = params[:vote_text].present? ? params[:vote_text] : nil #其他选项的内容
+ user_votes = question_votes.find_current_vote("user_id",current_user.id) #当前用户的答案,可能有多个
+ # 当前用户的当前答案,如果已存在,当再次点击的时候,取消答案,即删除该答案
+ current_vote_text = nil
+
+ if user_votes.find_vote_text.present?
+ current_vote_text = user_votes.find_vote_text.first
+ end
+
+ vote_answer_params = {
+ :user_id => current_user.id,
+ :poll_question_id => @poll_question.id,
+ :poll_answer_id => question_answer_id,
+ :vote_text => question_answer_text
+ }
+ #begin
+ if question_type == 1
+ if user_votes.present? #用户曾经回答过的,答案选择不一样,否则新建
+ current_user_answer = user_votes.first
+ if current_user_answer.poll_answer_id != question_answer_id #如果说更换了答案,则以前的答案删除,并新建记录
+ current_user_answer.destroy
+ PollVote.create(vote_answer_params)
+ else
+ if question_answer_text.present?
+ current_user_answer.update_attribute("vote_text", question_answer_text)
+ end
+ end
+ else
+ PollVote.create(vote_answer_params)
+ end
+ elsif question_type == 2 #多选题的话,答案应该是1个以上
+ question_answer_ids = params[:poll_answer_id] ? params[:poll_answer_id] : [] #该答案的id
+ if question_answer_ids.present?
+ if question_answer_text.present? #有文字输入,但是不存在其他选项的
+ ques_vote_id = question_answer_ids.map(&:to_i).max
+ if current_vote_text.present? #已有其他输入文字的选项
+ current_vote_text.update_attribute("vote_text", question_answer_text)
+ else
+ answer_option = {
+ :user_id => current_user.id,
+ :poll_question_id => @poll_question.id,
+ :poll_answer_id => ques_vote_id,
+ :vote_text => question_answer_text
+ }
+ PollVote.create(answer_option)
+ end
+ end
+
+ ea_ids = user_votes.pluck(:poll_answer_id)
+ common_answer_ids = question_answer_ids & ea_ids #已经存在的试卷选项id
+ new_ids = question_answer_ids - common_answer_ids # 新增的id
+ old_ids = ea_ids - common_answer_ids #没有选择的,则删掉
+ if new_ids.size > 0
+ new_ids.each do |e|
+ answer_option = {
+ :user_id => current_user.id,
+ :poll_question_id => @poll_question.id,
+ :poll_answer_id => e,
+ :vote_text => nil
+ }
+ ex_a = PollVote.new(answer_option)
+ ex_a.save!
+ end
+ end
+ if old_ids.size > 0
+ ea_answer = user_votes.find_current_vote("poll_answer_id",old_ids)
+ ea_answer.destroy_all
+ end
+ else
+ user_votes.destroy_all
+ end
+ else #主观题的输入
+ if current_vote_text.present?
+ if question_answer_text.present?
+ user_votes.first.update_attribute("vote_text", question_answer_text)
+ else
+ user_votes.destroy_all
+ end
+ else
+ PollVote.create(vote_answer_params)
+ end
+ end
+ @current_question_number = @poll_question.question_number
+ @current_question_necessary = @poll_question.is_necessary
+ #问答记录存在,且有值,才会有返回值。
+ @current_question_status = 0
+ if user_votes.present?
+ vote_answer_id = user_votes.pluck(:poll_answer_id).reject(&:blank?).size
+ vote_text_count = user_votes.pluck(:vote_text).reject(&:blank?).size
+ if vote_text_count > 0 || vote_answer_id > 0
+ @current_question_status = 1
+ end
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+
+ private
+
+ def get_poll_question
+ @poll_question = PollQuestion.find_by_id(params[:poll_question_id])
+ if @poll_question.blank?
+ normal_status(-1,"问卷试题不存在!")
+ else
+ @poll = @poll_question.poll
+ @course = @poll.course
+ if @poll.blank?
+ normal_status(-1,"问卷不存在!")
+ elsif @course.blank?
+ normal_status(-1,"课堂不存在!")
+ end
+ end
+
+ end
+
+ def check_answer_in_question
+ poll_answer_ids = @poll_question.poll_answers.pluck(:id)
+ if @poll_question.question_type == 1 #单选题/多选题
+ unless (params[:poll_answer_id].present? && poll_answer_ids.include?(params[:poll_answer_id].to_i)) || (params[:poll_answer_id].blank? && params[:vote_text].present?)
+ normal_status(-1, "答案ID错误!")
+ end
+ end
+ end
+
+ def check_multi_answers
+ if @poll_question.question_type == 2
+ user_vote_count = params[:poll_answer_id].size
+ if @poll_question.max_choices.present?
+ question_max_choices = @poll_question.max_choices
+ else
+ question_max_choices = 0
+ end
+ if question_max_choices > 0 && user_vote_count > question_max_choices
+ normal_status(-1,"多选题答案超过最大限制!")
+ end
+ end
+ end
+end
diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb
new file mode 100644
index 000000000..751b725e6
--- /dev/null
+++ b/app/controllers/polls_controller.rb
@@ -0,0 +1,1300 @@
+class PollsController < ApplicationController
+ # before_action :check_poll_status 问卷的发消息和定时任务没有做
+ before_action :require_login,except: [:index]
+ before_action :find_course, except: [:show,:poll_setting,:commit_setting,:edit,:update,:start_answer,:commit_poll,
+ :commit_result,:poll_lists,:cancel_publish,:cancel_publish_modal,:common_header]
+ before_action :get_poll_and_course, only: [:show,:poll_setting,:commit_setting,:edit,:update,:start_answer,
+ :commit_poll,:commit_result,:poll_lists,:cancel_publish,
+ :cancel_publish_modal,:common_header]
+ before_action :user_course_identity
+ before_action :is_course_teacher, except: [:index,:start_answer,:poll_setting,:commit_poll,:commit_result,:poll_lists,:common_header] #判断是否为课堂老师
+ before_action :check_user_status
+ before_action :is_course_public, only: [:set_public]
+ before_action :check_user_on_answer, only: [:show,:start_answer,:commit_poll,:poll_lists] #判断当前用户在问卷的权限/老师是否属于分班的权限
+ before_action :validate_params, only: [:create,:update]
+ before_action :validates_multi_ids, only: [:publish,:end_poll,:destroys,:set_public,:join_poll_banks]
+ before_action :check_poll_setting_status,only: [:commit_setting]
+ before_action :get_questions_count ,only: [:start_answer,:show,:commit_result,:edit]
+ before_action :check_user_id_start_answer,only: [:start_answer]
+ before_action :check_poll_question_complete,only: [:commit_poll] #问卷提交前来判断问题是否完成
+ before_action :check_poll_commit_result,only: [:commit_result]
+ before_action :get_all_polls_commit, only: [:commit_result] #该问卷全部的用户
+ before_action :get_left_banner_id, only:[:common_header,:start_answer,:new,:edit,:index]
+ include PollsHelper
+
+ def index
+ ActiveRecord::Base.transaction do
+ begin
+ # 按发布时间或创建时间排序
+ @polls_all = @course.polls
+ member_show_polls = @polls_all.publish_or_not # 已发布的或已截止的问卷
+ @current_user_ = current_user
+ @course_status = @course.is_end ? 0 : 1 # 课堂是否结束
+ @course_is_public = @course.is_public
+ @polls_count = @polls_all.count # 全部页面,需返回
+ @polls_unpublish_counts = @polls_all.poll_by_status(1).count #未发布的问卷数
+ @polls_published_counts = @polls_all.poll_by_status([2, 3]).count # 已发布的问卷数
+
+ # 课堂的学生人数
+ @course_all_members = @course.students #当前课堂的全部学生
+ @course_all_members_count = @course_all_members.count #当前课堂的学生数
+ @current_student = @course_all_members.find_by(user_id: current_user.id) #当前用户是否为课堂的学生
+
+ # polls的不同用户群体的显示
+ if @user_course_identity < Course::STUDENT # @is_teacher_or 1为老师/管理员/助教
+ @is_teacher_or = 1
+ @teacher_groups_ids = @course.teacher_course_groups.get_user_groups(current_user.id).pluck(:course_group_id).reject(&:blank?)
+ @polls = @polls_all #老师能看到全部的问卷,不管是已发布的/未发布的/已截止的/统一设置的/私有设置的(看到内容不同)
+ elsif @user_course_identity == Course::STUDENT # 2为课堂成员,能看到统一设置的和自己班级的
+ @is_teacher_or = 2
+ member_group_id = @current_student.try(:course_group_id).to_i # 成员的分班id,默认为0
+ if member_group_id == 0 #表示是课堂的未分班成员,只能查看统一设置的试卷(已发布的/已截止的)
+ @polls = member_show_polls.size > 0 ? member_show_polls.public_or_unset : []
+ else #已分班级的成员,可以查看统一设置和单独设置(试卷是发布在该班级)试卷
+ # 已发布 当前用户班级分组的 试卷id
+ poll_settings_ids = @course.poll_group_settings.where(course_group_id: member_group_id).poll_group_published.pluck(:poll_id).uniq # 选择成员的班级id等于课堂问卷设置的班级id
+ @polls = member_show_polls.present? ? member_show_polls.public_or_unset.or(member_show_polls.where(id: poll_settings_ids)) : []
+ end
+ else #用户未登陆或不是该课堂成员,仅显示统一设置的(已发布的/已截止的),如有公开,则不显示锁,不公开,则显示锁
+ @is_teacher_or = 0
+ @polls = member_show_polls.size > 0 ? member_show_polls.public_or_unset : []
+ end
+ if @polls.count > 0
+ if params[:type].present?
+ choose_type = params[:type]
+ member_group_id = @current_student.try(:course_group_id).to_i # 成员的分班id,默认为0
+ if @is_teacher_or == 2 && member_group_id > 0
+ poll_groups_sets = @course.poll_group_settings.where(course_group_id: member_group_id).poll_group_published
+ poll_settings_ids = poll_groups_sets.pluck(:poll_id)
+ poll_ended_ids = poll_groups_sets.poll_group_ended.pluck(:poll_id).uniq
+ # poll_settings_ids = @course.poll_group_settings.where(course_group_id: member_group_id).poll_group_ended.pluck(:poll_id).uniq
+ if choose_type.to_i == 2
+ @polls = @polls_all.present? ? @polls_all.poll_by_status(2).public_or_unset.or(@polls_all.where(id:(poll_settings_ids - poll_ended_ids).uniq)).distinct : []
+ elsif choose_type.to_i == 3
+ @polls = @polls_all.present? ? @polls_all.poll_by_status(3).public_or_unset.or(@polls_all.where(id: poll_ended_ids)).distinct : []
+ end
+ else
+ @polls = @polls.poll_by_status(choose_type)
+ end
+ end
+
+ if params[:search].present?
+ search_type = params[:search].to_s.strip
+ @polls = @polls.poll_search(search_type)
+ end
+
+ # 分页
+ @polls_select_count = @polls.size
+ @polls = @polls.order( "IF(ISNULL(publish_time),0,1), publish_time DESC,created_at DESC")
+ @page = params[:page] || 1
+ @limit = params[:limit] || 15
+
+ @polls = @polls.page(@page).per(@limit)
+ @polls = @polls.includes(:poll_users,:poll_questions,:poll_group_settings)
+
+ else
+ @polls = []
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def new
+ ActiveRecord::Base.transaction do
+ begin
+ @poll = Poll.new
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("问卷创建失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # polls_status 问卷状态,1为创建成功,但未发布;2为已发布;3表示已截止
+ # show_result true则在截止时间之后对课堂成员公开答题统计,0则不公开,默认为true
+ # is_public true 为公开/ false为私有,默认为false
+ # exercise_bank_id 试卷的id
+ # unified_setting true 统一设置 / false为分班设置,默认为true
+ # un_anonymous 是否实名,默认为false,即不公开
+ def create
+ ActiveRecord::Base.transaction do
+ begin
+ poll_name = params[:polls_name]
+ poll_desc = params[:polls_description]
+ poll_options = {
+ :polls_name => poll_name,
+ :polls_description => poll_desc,
+ :user_id => current_user.id,
+ :course_id => @course.id,
+ :polls_status => 1,
+ :polls_type => "Course",
+ }
+ @poll = Poll.create(poll_options)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("问卷创建失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def edit
+ ActiveRecord::Base.transaction do
+ begin
+ @poll_questions = @poll.poll_questions.order("question_number ASC")
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面请求失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def update
+ ActiveRecord::Base.transaction do
+ begin
+ poll_name = params[:polls_name]
+ poll_des = params[:polls_description]
+ poll_params = {
+ :polls_name => poll_name,
+ :polls_description => poll_des
+ }
+ @poll.update_attributes(poll_params)
+ normal_status(0,"问卷更新成功!")
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("没有权限")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #show页面应该是老师的题型预览页面及问题/答案的编辑,老师页面的
+ def show
+ ActiveRecord::Base.transaction do
+ begin
+ if @user_course_identity < Course::STUDENT
+ @is_teacher_or = 1
+ else
+ @is_teacher_or = 0
+ end
+ @poll_questions = @poll.poll_questions.order("question_number ASC")
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("没有权限")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def common_header
+ ActiveRecord::Base.transaction do
+ begin
+ if @user_course_identity > Course::ASSISTANT_PROFESSOR
+ @is_teacher_or = 0
+ @user_poll_answer = @poll.check_user_votes_status(current_user)
+ else
+ @is_teacher_or = 1
+ @user_poll_answer = 3 #教师页面
+ end
+ poll_status = @poll.get_poll_status(current_user.id)
+ poll_id_array = [@poll.id]
+ @poll_publish_count = get_user_permission_course(poll_id_array,2).count #是否存在已发布的
+ @poll_unpublish_count = get_user_permission_course(poll_id_array,1).count #是否存在未发布的
+
+ if (@poll_publish_count == 0) && (@poll_unpublish_count == 0) #即表示没有分班
+ if poll_status == 1
+ @poll_unpublish_count = 1 #试卷未发布,且课堂没有分班的时候
+ elsif poll_status == 2
+ @poll_publish_count = 1 #试卷未发布,且课堂没有分班的时候
+ end
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("没有权限")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #立即发布的弹窗内容
+ def publish_modal
+ ActiveRecord::Base.transaction do
+ begin
+ poll_ids = params[:check_ids]
+ if poll_ids.count > 0
+ @course_groups = get_user_permission_course(poll_ids,1)
+ else
+ @course_groups = []
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("没有权限")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+ #首页批量或单独 立即发布,应是跳出弹窗,设置开始时间和截止时间。
+ def publish
+ ActiveRecord::Base.transaction do
+ begin
+ check_ids = Poll.where(id: params[:check_ids])
+ ex_end_time = params[:end_time] || Time.at(((1.month.since.to_i)/3600.0).ceil * 3600)
+ check_ids.each do |poll|
+ if poll.unified_setting
+ pl_status = poll.polls_status #则为试卷的状态
+ else
+ pl_status = poll.poll_group_settings.find_in_poll_group("course_group_id",params[:group_ids]).poll_group_not_published.present? ? 1 : 0 #立即发布针对分组设置的全部未发布的班级才生效
+ end
+ if pl_status == 1 #如果问卷存在已发布的,或者是已截止的,那么则直接跳过
+ g_course = params[:group_ids] #表示是否传入分班参数,如果传入分班的参数,那么poll的统一设置需修改
+ if g_course
+ course_groups = @course.teacher_course_groups.get_user_groups(current_user.id)
+ if course_groups.blank?
+ user_course_groups = @course.course_groups.present? ? @course.course_groups.pluck(:id) : []
+ else
+ user_course_groups = course_groups.pluck(:course_group_id)
+ end
+ if g_course.map(&:to_i).sort == user_course_groups.sort # 如果是设置为全部班级,则问卷不用分组,且问卷设定为统一设置,否则则分组设置
+ poll.poll_group_settings.destroy_all
+ poll_unified = true
+ notify_student_ids = @course.students.pluck(:user_id)
+ else
+ poll_unified = false
+ g_course.each do |i|
+ poll_group_setting = poll.poll_group_settings.find_in_poll_group("course_group_id",i).first #根据课堂分班的id,寻找问卷所在的班级
+ if poll_group_setting #如果该问卷分组存在,则更新,否则新建
+ poll_group_setting.update_attributes(publish_time:Time.now,end_time:ex_end_time)
+ else
+ p_course_group = {
+ :poll_id => poll.id,
+ :course_group_id => i,
+ :course_id => poll.course.id,
+ :publish_time => Time.now,
+ :end_time => ex_end_time,
+ }
+ new_poll_group = poll.poll_group_settings.new p_course_group
+ new_poll_group.save
+ end
+ end
+
+ notify_student_ids = @course.students.where(course_group_id: params[:group_ids]).pluck(:user_id)
+ end
+ else
+ poll.poll_group_settings.destroy_all
+ poll_unified = true
+ notify_student_ids = @course.students.pluck(:user_id)
+ end
+ if poll.end_time.blank?
+ e_time = ex_end_time
+ elsif poll.poll_group_settings.end_time_present.count > 0 # 该问卷分组有结束时间为空的
+ e_time = poll.poll_group_settings.end_time_present.map(&:end_time).max
+ else
+ e_time = poll.end_time
+ end
+ poll_status = set_poll_status(Time.now,e_time)
+
+ poll_params = {
+ :publish_time => Time.now,
+ :end_time => e_time,
+ :polls_status => poll_status,
+ :unified_setting => poll_unified
+ }
+ poll.update_attributes(poll_params)
+ if poll.course_acts.size == 0
+ poll.course_acts << CourseActivity.new(:user_id => poll.user_id,:course_id => poll.course_id)
+ end
+ PollPublishNotifyJob.perform_later(poll.id, notify_student_ids)
+ end
+ end
+ normal_status(0, "问卷发布成功!")
+
+ ## 需添加发送消息的接口,稍后添加
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("问卷发布失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #立即截止的弹窗内容
+ def end_poll_modal
+ ActiveRecord::Base.transaction do
+ begin
+ poll_ids = params[:check_ids]
+ if poll_ids.count > 0
+ @course_groups = get_user_permission_course(poll_ids,3)
+ else
+ @course_groups = []
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("没有权限")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 首页批量或单独 立即截止,截止时间为当前时间
+ def end_poll
+ ActiveRecord::Base.transaction do
+ begin
+ check_ids = Poll.where(id: params[:check_ids])
+ check_ids.each do |poll|
+ poll_status = poll.get_poll_status(current_user.id)
+ if poll_status == 2 #跳过已截止的或未发布的
+ g_course = params[:group_ids] #表示是否传入分班参数,如果传入分班的参数,那么poll的统一设置需修改,poll_group_settings
+ if g_course
+ # 如果问卷之前是统一设置且分班参数与课堂的分班数不一致,则变为非统一设置,同时创建poll_group_settings
+ if poll.unified_setting && g_course.length != @course.course_groups_count
+ poll.poll_group_settings.destroy_all
+ @course.course_groups.each do |group|
+ poll.poll_group_settings << PollGroupSetting.new(poll_id: poll.id, course_group_id: group.id, course_id: @course.id,
+ publish_time: poll.publish_time, end_time: poll.end_time)
+ end
+ else
+ poll_unified = poll.unified_setting #这个作为初始化的值是否要好点呢?-hs
+ end
+ poll_users = poll_unified ? poll.poll_users :
+ poll.poll_users.joins("join course_members on poll_users.user_id = course_members.user_id").where(course_members: {course_group_id: g_course, role: 4})
+ poll_group_setting = poll.poll_group_settings
+ old_poll_groups = poll_group_setting.find_in_poll_group("course_group_id",g_course) #问卷分组的截止时间更改
+ old_poll_groups.update_all(:end_time => Time.now)
+ new_end_time = poll_group_setting.end_time_present.map(&:end_time) # 问卷结束时间不为空的
+ new_end_time_s = new_end_time.size > 0 ? new_end_time.max : Time.now
+ new_poll_status = set_poll_status(poll.publish_time,new_end_time_s)
+ poll.update_attributes(:end_time => new_end_time_s,:polls_status => new_poll_status,:unified_setting => poll_unified)
+ elsif poll.unified_setting
+ poll_users = poll.poll_users
+ poll.update_attributes(:polls_status => 3, :end_time => Time.now)
+ end
+ poll_users = poll_users.where("commit_status = 0 and start_at is not null")
+ poll_users.update_all(:commit_status => 1, :end_at => Time.now)
+ end
+ end
+ normal_status(0, "问卷截止成功!")
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("立即截止失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #撤销发布的弹窗内容
+ def cancel_publish_modal
+ ActiveRecord::Base.transaction do
+ begin
+ poll_ids = [@poll.id]
+ if poll_ids.count > 0
+ @course_groups = get_user_permission_course(poll_ids,2)
+ else
+ @course_groups = []
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("没有权限")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 撤销发布,仅指当前的问卷
+ def cancel_publish
+ ActiveRecord::Base.transaction do
+ begin
+ g_course = params[:group_ids]
+ if !@poll.unified_setting #如果问卷为分班设置,且传入的班级id存在
+ if g_course.present?
+ course_group_ids = @course.course_members.course_find_by_ids("course_group_id",g_course).pluck(:user_id).uniq #传入班级的全部学生人数
+ all_poll_settings = @poll.poll_group_settings.find_in_poll_group("course_group_id",g_course) #根据传入的班级id来获取问卷的分组设置
+ all_poll_settings.destroy_all #问卷的设置文件全部删除
+ # @poll.poll_users.find_by_group_ids(course_group_ids).destroy_all #问卷的提交信息
+ poll_question_ids = @poll.poll_questions.pluck(:id) #问卷的全部问卷
+ PollVote.find_current_vote("user_id",course_group_ids).find_current_vote("poll_question_id",poll_question_ids).destroy_all
+ poll_user_options = {
+ :commit_status => 0,
+ :start_at => nil,
+ :end_at => nil
+ }
+ @poll.poll_users.find_by_group_ids(course_group_ids).update_all(poll_user_options) #试卷的用户全部更新为未答题状态
+ poll_groups = @poll.poll_group_settings
+ if poll_groups.present? #是否还有问卷的发布分组,如果有的话,则是根据发布规则取最大和最小值
+ unified_setting = false
+ e_time_present = poll_groups.end_time_present.map(&:end_time)
+ p_time_present = poll_groups.publish_time_present.map(&:publish_time)
+ e_time = e_time_present.size > 0 ? e_time_present.max : nil
+ p_time = p_time_present.size > 0 ? p_time_present.min : nil
+ poll_status = 1
+ if p_time.nil? #发布时间为空,则表示问卷未发布
+ poll_status = 1
+ elsif p_time.present? && e_time.present?
+ poll_status = set_poll_status(p_time,e_time)
+ end
+ else
+ unified_setting = true
+ p_time = nil
+ e_time = nil
+ poll_status = 1
+ end
+ poll_options = {
+ :unified_setting => unified_setting,
+ :polls_status => poll_status,
+ :publish_time => p_time,
+ :end_time => e_time
+ }
+ @poll.update_attributes(poll_options)
+ normal_status(0,"分班问卷撤销发布成功!")
+ else
+ normal_status(-1,"请选择撤销发布班级!")
+ end
+ else
+ poll_user_options = {
+ :commit_status => 0,
+ :start_at => nil,
+ :end_at => nil
+ }
+ @poll.poll_users.update_all(poll_user_options)
+ poll_question_ids = @poll.poll_questions.pluck(:id)
+ PollVote.find_current_vote("poll_question_id",poll_question_ids).destroy_all
+ poll_new_params = {
+ :polls_status => 1,
+ :publish_time => nil,
+ :end_time => nil,
+ :unified_setting => true
+ }
+ @poll.update_attributes(poll_new_params)
+ @poll.poll_group_settings.destroy_all
+ normal_status(0,"问卷撤销发布成功!")
+
+ ## @poll.tidings.destroy_all 用户的发送消息全部删除,因接口未定,所以先注释
+
+ ## 需添加发送消息的接口,稍后添加
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("问卷撤销发布失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 首页批量或单独删除
+ def destroys
+ ActiveRecord::Base.transaction do
+ begin
+ check_ids = Poll.where(id:params[:check_ids])
+ check_ids.each do |poll|
+ if poll.present?
+ poll.destroy
+ end
+ end
+ normal_status(0, "问卷已删除成功!")
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("问卷删除失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 设为公开
+ def set_public
+ ActiveRecord::Base.transaction do
+ begin
+ check_ids = Poll.where(id:params[:check_ids])
+ check_ids.each do |poll|
+ poll.update_attribute('is_public', true)
+ end
+ normal_status(0, "问卷已设为公开!")
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("问卷设为公开失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ ## 加入题库
+ def join_poll_banks
+ ActiveRecord::Base.transaction do
+ begin
+ check_ids = Poll.where(id: params[:check_ids])
+ check_ids.each do |poll|
+ current_ex_bank = current_user.exercise_banks.find_by_container(poll.id,"Poll").first
+ if current_ex_bank.present? #当前用户的选择问卷是否已加入习题库,存在则更新习题库和问题库,否则新建习题库和问题库
+ ex_params = {
+ :name => poll.polls_name,
+ :description => poll.polls_description,
+ :course_list_id => poll.course.try(:course_list_id)
+ }
+ current_ex_bank.update_attributes(ex_params)
+ question_bank = QuestionBank.ques_by_container(current_ex_bank.id,current_ex_bank.container_type) #该习题库是否存在于问题库里
+ ques_params = {
+ :name => current_ex_bank.name,
+ :course_list_id => current_ex_bank.course_list_id
+ }
+ question_bank.first.update_attributes(ques_params) if question_bank.present?
+ current_ex_bank.exercise_bank_questions.destroy_all # 更新后,习题库的问题全部删除,后续重新再建
+ else
+ ex_params = {
+ :name => poll.polls_name,
+ :description => poll.polls_description,
+ :user_id => current_user.id,
+ :is_public => 0,
+ :course_list_id => poll.course.try(:course_list_id),
+ :container_id => poll.id,
+ :container_type => "Poll",
+ :quotes => 1
+ }
+ current_ex_bank= ExerciseBank.new ex_params
+ if current_ex_bank.save #如果习题库保存成功,则会创建问题库question_bank
+ ques_params = {
+ :name => current_ex_bank.name,
+ :container_id => current_ex_bank.id,
+ :container_type => current_ex_bank.container_type,
+ :quotes => current_ex_bank.quotes,
+ :user_id => current_ex_bank.user_id,
+ :is_public => current_ex_bank.is_public,
+ :course_list_id => current_ex_bank.course_list_id
+ }
+ question_bank = QuestionBank.new ques_params
+ question_bank.save
+ end
+ end
+ # 问卷的问题的输入
+ poll.poll_questions.each do |f|
+ option = {
+ :question_title => f.question_title,
+ :question_type => f.question_type,
+ :is_necessary => f.is_necessary,
+ :question_number => f.question_number,
+ :max_choices => f.max_choices,
+ :min_choices => f.min_choices
+ }
+ exercise_bank_question = current_ex_bank.exercise_bank_questions.new option
+ exercise_bank_question.save
+ ## 问卷答案的输入
+ f.poll_answers.each do |a|
+ choice_option = {
+ :choice_position => a.answer_position,
+ :choice_text => a.answer_text
+ }
+ exercise_bank_c = exercise_bank_question.exercise_bank_choices.new choice_option
+ exercise_bank_c.save
+ end
+ end
+ end
+ normal_status(0, "题库更新成功!")
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("题库更新失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 我的问卷题库
+ def my_polls
+ ActiveRecord::Base.transaction do
+ begin
+ ## 我的问卷题库
+ @current_user_exercises = current_user.exercise_banks.find_by_c_type("Poll")
+ if @current_user_exercises.present?
+
+ if params[:search].present?
+ search_type = params[:search].to_s.strip
+ @current_user_exercises = @current_user_exercises.exercise_bank_search(search_type)
+ end
+ page = params[:page] || 1
+ limit = params[:limit] || 15
+ @my_exercises_count = @current_user_exercises.size
+ @current_user_exercises = @current_user_exercises.page(page).per(limit)
+ else
+ @current_user_exercises = []
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("题库调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 公共的问卷题库
+ def public_polls
+ ActiveRecord::Base.transaction do
+ begin
+ if current_user.is_certification_teacher
+ @user_certification = 1 #用户已通过认证
+ @public_exercises = ExerciseBank.find_by_c_type("Poll").public_exercises
+ if @public_exercises.present?
+
+ if params[:search].present?
+ search_type = params[:search].to_s.strip
+ @public_exercises = @public_exercises.exercise_bank_search(search_type)
+ end
+
+ page = params[:page] || 1
+ limit = params[:limit] || 15
+
+ @public_exercises_count = @public_exercises.size
+ @public_exercises = @public_exercises.page(page).per(limit)
+ else
+ @public_exercises_count = 0
+ @public_exercises = []
+ end
+ else
+ @user_certification = 0 #用户未通过认证
+ @public_exercises_count = 0
+ @public_exercises = []
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("题库调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 设置页面,先返回问卷的状态
+ def poll_setting
+ ActiveRecord::Base.transaction do
+ begin
+ @user_permission = 2
+ @user_course_groups = @course.teacher_group(current_user.id) #当前老师的分班
+ @being_setting_course_ids = @poll.poll_published_ids(current_user.id) #当前用户已发布的班级的id
+ @user_published_setting = @poll.poll_group_settings.find_in_poll_group("course_group_id",@being_setting_course_ids)
+ poll_ids = [@poll.id]
+ @poll_publish_count = get_user_permission_course(poll_ids,2).count
+ ## 需添加发送消息的接口,稍后添加
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #问卷设置提交
+ def commit_setting
+ ActiveRecord::Base.transaction do
+ begin
+ error_count = 0 # 判断循环里是否有已发布/已截止的,且时间更改了的分班。
+
+ course_group_ids = @course.teacher_course_group_ids(current_user.id) #当前老师的班级id数组
+
+ poll_status = @poll.get_poll_status(current_user.id)
+ if poll_status == 1 && (course_group_ids - [0]).count > 0 # 问卷未发布,且老师的分班大于1 ,才可以修改统一设置,否则按poll默认的来处理
+ unified_setting = params[:unified_setting]
+ else
+ unified_setting = @poll.unified_setting
+ end
+ show_result = params[:show_result] ? 1 : 0
+ un_anonymous = params[:un_anonymous] ? true : false
+ # 统一设置或者分班为0,则更新问卷,并删除问卷分组
+ if unified_setting || (course_group_ids.size == 0)
+ params_publish_time = params[:publish_time].present? ? params[:publish_time].to_time : nil
+ params_end_time = nil
+ if params[:end_time].blank?
+ if params_publish_time.present?
+ params_end_time = params_publish_time + 30.days
+ end
+ else
+ params_end_time = params[:end_time].to_time
+ end
+ # params_end_time = params[:end_time].present? ? params[:end_time].to_time : nil
+ if poll_status == 2 && @poll.publish_time != params_publish_time
+ normal_status(-1,"不允许修改发布时间")
+ elsif poll_status == 3 && (@poll.end_time != params_end_time || @poll.publish_time != params_publish_time)
+ normal_status(-1,"不允许修改发布时间")
+ elsif params_publish_time.present? && params_end_time.present? && params_end_time < params_publish_time
+ normal_status(-1,"截止时间不能小于发布时间")
+ else
+ #发布时间小于当前时间,则poll显示为未发布,当截止时间大于当前时间,则显示为已截止
+ poll_status_n = set_poll_status(params_publish_time,params_end_time)
+ poll_params = {
+ :unified_setting => unified_setting,
+ :show_result => show_result,
+ :un_anonymous => un_anonymous,
+ :polls_status => poll_status_n,
+ :publish_time => params_publish_time,
+ :end_time => params_end_time
+ }
+ @poll.update_attributes(poll_params)
+ @poll.poll_group_settings.destroy_all
+ normal_status(0, "问卷设置成功!")
+ end
+ else
+ params_times = params[:publish_time_groups] #分班返回的json数组{"publish_time_groups":[{"course_group_id":["1","2"],"publish_time":"xx","end_time":"xxx"}]}
+ poll_groups = @poll.poll_group_settings.find_in_poll_group("course_id",@course.id) #当前课堂问卷的班级全部分班信息
+ poll_groups_ids = poll_groups.pluck(:course_group_id) #问卷的全部分班id
+ total_common = params_times.map{|k| k[:course_group_id]}.sum #传入的所有分组的分班id
+ total_common_group = poll_groups_ids & total_common #传入的分班与问卷已存在的分班的交集
+ old_poll_groups = poll_groups_ids - total_common_group #后来传入的分班里,没有了的班级,即需要删除
+ params_times.each do |t|
+ course_id = t[:course_group_id] #为数组,可能会设置分班为各个班级id的数组
+ poll_publish_time = t[:publish_time].present? ? t[:publish_time].to_time : nil
+ # poll_end_time = t[:end_time].present? ? t[:end_time].to_time : nil
+ poll_end_time = nil
+ if t[:end_time].blank?
+ if poll_publish_time.present?
+ poll_end_time = poll_publish_time + 30.days
+ end
+ else
+ poll_end_time = t[:end_time].to_time
+ end
+ poll_group = poll_groups.find_in_poll_group("course_group_id",course_id) #判断该分班是否存在
+ if poll_group.present? && poll_group.first.end_time <= Time.now && (poll_end_time != poll_group.first.end_time || poll_publish_time != poll_group.first.publish_time) #已截止且时间改变的,则提示错误
+ error_count += 1
+ end
+ if poll_group.present? && poll_group.first.publish_time < Time.now && poll_publish_time != poll_group.first.publish_time
+ error_count += 1
+ end
+ if error_count == 0
+ common_group = poll_groups_ids & course_id #传入的班级与问卷已存在的班级的交集,即表示已有分班的
+ new_group_ids = course_id - common_group #新传入的班级id
+ if common_group.size > 0 #传入的参数存在已发布的分班
+ poll_group_params = {
+ :publish_time => poll_publish_time,
+ :end_time => poll_end_time
+ }
+ poll_group = poll_groups.find_in_poll_group("course_group_id",common_group)
+ the_group_setting = poll_group.first
+ if the_group_setting.present?
+ the_group_setting_status = set_poll_status(the_group_setting.publish_time,the_group_setting.end_time)
+ if the_group_setting_status == 2
+ poll_group_params = {
+ :publish_time => the_group_setting.publish_time,
+ :end_time => poll_end_time
+ }
+ elsif the_group_setting_status == 3
+ poll_group_params = {
+ :publish_time => the_group_setting.publish_time,
+ :end_time => the_group_setting.end_time
+ }
+ end
+ end
+ poll_group.update_all(poll_group_params)
+ end
+ if new_group_ids.size > 0
+ new_group_ids.each do |c|
+ poll_group_params = {
+ :poll_id => @poll.id,
+ :course_group_id => c,
+ :course_id => @course.id,
+ :publish_time => poll_publish_time,
+ :end_time => poll_end_time
+ }
+ new_poll_group = PollGroupSetting.new(poll_group_params)
+ new_poll_group.save
+ end
+ end
+ end
+ end
+
+ if error_count > 0
+ error_count == 0
+ normal_status(-1,"已发布/已截止的问卷不允许修改时间")
+ else
+ if old_poll_groups.size > 0
+ old_all_poll_groups = poll_groups.find_in_poll_group("course_group_id",old_poll_groups)
+ old_all_poll_groups.destroy_all
+ end
+ #问卷更新为poll_group_setting的发布时间最小,截止时间最大
+ e_time_present = poll_groups.end_time_present.map(&:end_time)
+ p_time_present = poll_groups.publish_time_present.map(&:publish_time)
+ e_time = e_time_present.size > 0 ? e_time_present.max : nil
+ p_time = p_time_present.size > 0 ? p_time_present.min : nil
+ poll_status = 1
+ if p_time.nil? #发布时间为空,则表示问卷未发布
+ poll_status = 1
+ elsif p_time.present? && e_time.present?
+ poll_status = set_poll_status(p_time,e_time)
+ end
+
+ poll_params = {
+ :unified_setting => unified_setting,
+ :show_result => show_result,
+ :un_anonymous => un_anonymous,
+ :polls_status => poll_status,
+ :publish_time => p_time,
+ :end_time => e_time
+ }
+ @poll.update_attributes(poll_params)
+ if @poll.polls_status == 2
+ if @poll.course_acts.size == 0
+ @poll.course_acts << CourseActivity.new(:user_id => @poll.user_id,:course_id => @poll.course_id)
+ end
+ end
+ normal_status(0, "问卷设置成功!")
+ end
+
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("提交出现错误!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 开始答题,如答案提交后则不可再修改, 问卷截止后或提交后,该页面不可再次点击
+ def start_answer
+ ActiveRecord::Base.transaction do
+ begin
+ poll_user_current = @poll.poll_users.find_by_group_ids(@poll_current_user_id).first #查找当前用户是否有过答题
+ @poll_status = @poll.get_poll_status(current_user.id)
+ if poll_user_current.blank?
+ if @user_course_identity > Course::ASSISTANT_PROFESSOR #当为老师的时候,不创建poll_user表,理论上老师是不能进入答题的
+ poll_user_params = {
+ :user_id => @poll_current_user_id,
+ :poll_id => @poll.id,
+ :start_at => Time.now,
+ :commit_status => 0
+ }
+ PollUser.create(poll_user_params)
+ end
+ elsif poll_user_current.start_at.nil?
+ poll_user_current.update_attributes(:start_at => Time.now)
+ end
+
+ if @user_course_identity < Course::STUDENT || (@poll_status == 3) || (poll_user_current.present? && poll_user_current.commit_status == 1)
+ @user_poll_status = 1 #当前用户为老师/问卷已截止/问卷已提交不可编辑
+ else
+ @user_poll_status = 0 #可编辑
+ end
+
+ # @answer_user = User.find_by(id:@poll_current_user_id)
+ @answer_status = []
+ question_answered = 0
+
+ # 判断是否已经回答还是新建的回答
+ @poll_questions.each do |q|
+ ques_vote = q.poll_votes.find_current_vote("user_id",@poll_current_user_id)
+ if ques_vote.present?
+ ques_status = 1
+ question_answered += 1
+ else
+ ques_status = 0
+ end
+ answer_status = {
+ :ques_id => q.id,
+ :ques_number => q.question_number,
+ :ques_status => ques_status
+ }
+ @answer_status = @answer_status.push(answer_status)
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 用户提交问卷
+ def commit_poll
+ ActiveRecord::Base.transaction do
+ begin
+ poll_user_current = @poll.poll_users.find_by_group_ids(current_user.id).first
+ poll_user_params = {
+ :commit_status => 1,
+ :end_at => Time.now
+ }
+ poll_user_current.update_attributes(poll_user_params)
+ normal_status(0, "问卷提交成功!")
+ ## 需添加发送消息的接口,稍后添加
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #问卷的统计结果
+ def commit_result
+ ActiveRecord::Base.transaction do
+ begin
+ # 分页
+ @page = params[:page] || 1
+ @limit = params[:limit] || 10
+ @poll_export_questions = @poll_questions.order("question_number ASC")
+ @poll_questions = @poll_questions.page(@page).per(@limit)
+ respond_to do |format|
+ format.json
+ format.xlsx{
+ if @user_course_identity > Course::ASSISTANT_PROFESSOR
+ tip_exception(403,"无权限操作")
+ else
+ polls_export_name = current_user.real_name + "_" + @course.name + "_" + @poll.polls_name + "_" + Time.now.strftime('%Y%m%d_%H%M%S')
+ render xlsx: "#{polls_export_name.strip.first(30)}",template: "polls/commit_result.xlsx.axlsx",locals: {poll_questions:@poll_export_questions}
+ end
+ }
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #问卷的答题列表
+ def poll_lists
+ ActiveRecord::Base.transaction do
+ begin
+ logger.info("########___________@course.teacher_course_groups)________________####################{@course.teacher_course_groups.where(user_id:current_user.id).pluck(:course_group_id)}")
+ poll_ids = [@poll.id]
+ @poll_list_status = @poll.get_poll_status(current_user.id)
+ @poll_publish_count = get_user_permission_course(poll_ids,2).count
+ @poll_unpublish_count = get_user_permission_course(poll_ids,1).count
+ @course_all_members = @course.students
+ if @user_course_identity < Course::STUDENT #当前为老师,而且老师只能查看自己班级的/课堂的问卷
+ @poll_current_user_status = 0
+ @poll_users_list = @poll.all_poll_users(current_user.id).distinct #该老师分班的全部学生
+ get_poll_answers(@poll_users_list)
+
+ if @poll_list_status == 1
+ @poll_course_groups =[]
+ else
+ poll_common_ids = @poll.poll_published_ids(current_user.id)
+ @poll_course_groups = @course.get_ex_published_course(poll_common_ids)
+ end
+ if @poll_list_status == 1
+ @poll_users_list = []
+ end
+ elsif @user_course_identity > Course::ASSISTANT_PROFESSOR
+ @poll_all_users = @poll.get_poll_exercise_users
+ get_poll_answers(@poll_all_users) # 未答和已答的
+ @poll_course_groups = [] #当为学生的时候,不显示分班情况
+ @poll_current_user_status = 1 #当前用户的状态,为学生
+ poll_current_user = @poll_all_users.find_by_group_ids(current_user.id) #当前用户是否开始做问卷(提交/未提交/没做)
+ if poll_current_user.present? && @poll_list_status != 1 #当前为学生或者有过答题的(提交/未提交),且问卷已发布的
+ @poll_users_list = poll_current_user.distinct
+ else
+ @poll_users_list = []
+ end
+ else
+ @poll_all_users = @poll.get_poll_exercise_users
+ get_poll_answers(@poll_all_users) # 未答和已答的
+ @poll_current_user_status = 2 #当前用户非课堂成员
+ @poll_users_list = []
+ end
+
+ if @poll_users_list.present? && @poll_users_list.count > 0
+ @poll_users_count = @poll_users_list.count #当前显示的全部成员数量
+ else
+ @poll_users_count = 0
+ end
+
+ if @poll_unanswers < 0
+ @poll_unanswers = 0
+ end
+
+ #筛选/分类,排序
+ order = params[:order]
+ # b_sort = params[:sort] || "desc"
+ choose_type = params[:commit_status]
+ group_id = params[:poll_group_id]
+ search_content = params[:search]
+
+ if @poll_users_list.present? && @poll_users_list.count > 0
+
+ if order == "student_id"
+ @poll_users_list = @poll_users_list.joins(user: [:user_extension]).order("user_extensions.student_id DESC")
+ else
+ @poll_users_list = @poll_users_list.order("end_at DESC")
+ end
+
+ #答题状态的选择
+ if choose_type.present?
+ @poll_users_list = @poll_users_list.commit_by_status(choose_type)
+ end
+
+ #班级的选择
+ if group_id.present?
+ poll_students = @course_all_members.course_find_by_ids("course_group_id",group_id) # 问卷所分班的全部人数
+ user_ids = poll_students.pluck(:user_id).reject(&:blank?)
+ @poll_users_list = @poll_users_list.find_by_group_ids(user_ids)
+ end
+
+ #搜索
+ if search_content.present?
+ #搜索用户的nickname,如果存在则返回,否则继续查询用户的真实姓名或学生号
+ nick_name_search = @poll_users_list.where(user_id: User.where('CONCAT(users.lastname, users.firstname) like ?',"%#{search_content}%"))
+ if nick_name_search.present?
+ @poll_users_list = nick_name_search
+ else
+ @poll_users_list = @poll_users_list.joins(user: [:user_extension]).where('user_extensions.student_id like ? OR user_extensions.student_realname like ?',"%#{search_content}%","%#{search_content}%")
+ end
+ end
+
+ @poll_users_size = @poll_users_list.count
+ # 分页
+ @page = params[:page] || 1
+ @limit = params[:limit] || 20
+ @poll_users_list = @poll_users_list.page(@page).per(@limit)
+ else
+ @poll_users_list = []
+ @poll_users_size = 0
+ end
+
+ @current_user = current_user
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("页面调用失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ private
+
+ def is_course_teacher
+ unless @user_course_identity < Course::STUDENT #为老师/助教/管理员
+ tip_exception("403", "")
+ end
+
+ end
+
+ def validate_params
+ normal_status(-1, "问卷标题不能为空!") if params[:polls_name].blank?
+ normal_status(-1, "问卷标题不能超过60个字符") if (params[:polls_name].present? && params[:polls_name].length > 60)
+ normal_status(-1, "问卷描述不能超过100个字符") if (params[:polls_description].present? && params[:polls_description].length > 100)
+ end
+
+ def poll_params
+ params.require(:poll).permit(:polls_name,:polls_status,:publish_time,:end_time,:polls_description,
+ :show_result,:exercise_bank_id,:is_public,:unified_setting,:un_anonymous)
+ end
+
+ # 获得问卷及课堂
+ def get_poll_and_course
+ @poll = Poll.find_by(id: params[:id])
+ if @poll.blank?
+ tip_exception(404)
+ else
+ @course = @poll.course
+ tip_exception(404) if @course.blank?
+ end
+ end
+
+ # 当前课堂及用户的判断
+ def check_user_status
+ unless @course.is_public == 1 || (@course.is_public == 0 && @user_course_identity < Course::NORMAL) #课堂的成员/管理员才可以
+ tip_exception(403)
+ end
+ end
+
+ # 在设置问卷公开前,需判断课堂是否公开
+ def is_course_public
+ unless @course.is_public == 1 # 0为私有,1为公开
+ tip_exception(403)
+ end
+ end
+
+ ## 判断开始答题页面的用户权限
+ def check_user_on_answer
+ poll_status = @poll.get_poll_status(current_user.id)
+ if @user_course_identity == Course::STUDENT && poll_status == 1 #问卷未发布,且当前用户不为老师/管理员
+ normal_status(-1, "未发布问卷!")
+ elsif @user_course_identity > Course::STUDENT && (!@poll.is_public || (@poll.is_public && !@poll.unified_setting)) ##不为课堂成员,且问卷不为公开的,或问卷公开,但是不是统一设置的
+ normal_status(-1, "问卷暂未公开!")
+ end
+ end
+
+ def check_unified_setting?(poll) #问卷是否统一设置,如为分班设置,当前用户是否在班级内
+ if !poll.unified_setting #如果为分班设置,则需判断用户是否在班级内
+ poll_group_settings = poll.poll_group_settings.pluck(:course_group_id)
+ member = @course.course_members.course_find_by_ids("user_id",current_user.id)
+ member_group_id = member.pluck(:course_group_id).uniq
+ if (member_group_id & poll_group_settings).size > 0 || @user_course_identity < Course::STUDENT
+ true
+ else
+ false
+ end
+ else
+ true
+ end
+ end
+
+ def validates_multi_ids
+ if params[:check_ids].blank? || params[:check_ids].count <= 0
+ normal_status(-1, "请选择问卷!")
+ elsif params[:check_ids].count > 15
+ normal_status(-1, "最多只能选择15条数据!")
+ end
+ end
+
+ def set_poll_status(publish_time,end_time)
+ time_now_i = Time.now
+ if publish_time.present? && end_time.present? && publish_time <= time_now_i && end_time > time_now_i
+ 2
+ elsif publish_time.nil? || (publish_time.present? && publish_time > time_now_i)
+ 1
+ elsif end_time.present? && end_time <= time_now_i
+ 3
+ # elsif end_time.present? && publish_time.present? && end_time < publish_time
+ # normal_status(-1,"时间设置错误!")
+ else
+ 1
+ end
+ end
+
+ def get_questions_count
+ @poll_questions = @poll.poll_questions.order("question_number ASC")
+ @poll_questions_count = @poll_questions.count # 全部的题目数
+ @poll_question_singles = @poll_questions.ques_count(1).all.count # 单选题
+ @poll_question_doubles = @poll_questions.ques_count(2).all.count # 多选题
+ @poll_question_mains = @poll_questions.ques_count(3).all.count #主观题
+ end
+
+ def check_poll_question_complete #commit_poll 的权限
+ poll_user_current = @poll.poll_users.find_by_group_ids(current_user.id).first
+ poll_status = @poll.get_poll_status(current_user.id)
+ if @user_course_identity < Course::STUDENT || (poll_status == 3) || (poll_user_current.present? && poll_user_current.commit_status == 1)
+ normal_status(-1,"用户没有权限!") #老师/管理员在提交时没有权限
+ else
+ necessary_answer = 0
+ poll_questions = @poll.poll_questions
+ necessary_question = poll_questions.ques_necessary # 问卷必答的问题
+ necessary_question.each do |q|
+ user_vote = q.poll_votes.find_current_vote("user_id",current_user.id)
+ vote_answer_id = user_vote.pluck(:poll_answer_id).reject(&:blank?).size
+ vote_text_count = user_vote.pluck(:vote_text).reject(&:blank?).size
+ if vote_answer_id == 0 && vote_text_count == 0
+ necessary_answer += 1
+ end
+ end
+ if necessary_answer > 0
+ normal_status(-1,"问卷提交失败,有#{necessary_answer}个未答的必答题!")
+ end
+ end
+ end
+
+ def check_poll_commit_result
+ poll_status = @poll.get_poll_status(current_user.id)
+ commit_poll_user = @poll.poll_users.find_by_group_ids(current_user.id).commit_by_status(1) #当前用户已提交问卷的
+ unless @user_course_identity < Course::STUDENT || (@poll.show_result && poll_status == 3 && commit_poll_user.present?)
+ normal_status(-1,"没有权限!") #当前为老师/问卷公开统计,且问卷已截止,且用户有过回答的
+ end
+ end
+
+ def check_user_id_start_answer #判断用户在开始答题时,是否有用户id传入,如果为老师,则id必需,否则为当前用户的id
+ user_login = params[:login]
+ # @poll_current_user_id = params[:user_id]
+ if user_login.blank? && @user_course_identity < Course::STUDENT #id不存在,且当前为老师/管理员等
+ normal_status(-1,"请输入学生登陆名!")
+ else
+ @answer_user = User.find_by(login:user_login)
+ if @answer_user.blank?
+ normal_status(404,"答题用户不存在")
+ else
+ @poll_current_user_id = @answer_user.id || current_user.id
+ end
+ end
+ end
+
+ def get_all_polls_commit
+ @poll_questions = @poll.poll_questions.order("question_number ASC")
+ @poll_commit_ids = @poll.poll_users.commit_by_status(1).pluck(:user_id) #问卷提交用户的id
+ # 全部页面,需返回
+ @poll_questions_count = @poll_questions.size
+ end
+
+ def get_user_permission_course(poll_ids,status) #获取用户权限范围内的已发布/未发布
+ poll_status = status.to_i
+ unpublish_group = []
+ g_course_ids = @course.teacher_course_groups.get_user_groups(current_user.id).pluck(:course_group_id).reject(&:blank?).uniq
+ if g_course_ids.blank? || g_course_ids.include?(0) #当前用户的分班权限为空,即具体全部的分班权限
+ user_groups_id = @course.course_groups.pluck(:id)
+ else
+ user_groups_id = g_course_ids
+ end
+ all_polls = Poll.where(id:poll_ids)
+ all_polls.each do |poll|
+ if poll.present?
+ if poll.unified_setting
+ poll_user_status = poll.get_poll_status(current_user.id) #当前用户的能看到的试卷
+ if poll_user_status == poll_status || poll_status == 3 #未发布的情况
+ unpublish_group = unpublish_group + user_groups_id
+ else
+ unpublish_group = []
+ end
+ # unpublish_group = unpublish_group + user_groups_id
+ else
+ poll_all_group_settings = poll.poll_group_settings
+ poll_group_settings = poll_all_group_settings.poll_group_published.pluck(:course_group_id).uniq #问卷设置已发布的班级
+ if poll_status == 1
+ unpublish_group = user_groups_id - poll_group_settings
+ elsif poll_status == 3
+ poll_ended_groups = poll_all_group_settings.poll_group_ended.pluck(:course_group_id).uniq
+ poll_and_user = user_groups_id & poll_group_settings #用户已设置的分班
+ unpublish_group = unpublish_group + poll_and_user - poll_ended_groups #已发布的全部班级减去截止的全部班级
+ else
+ poll_and_user = user_groups_id & poll_group_settings #用户已设置的分班
+ unpublish_group = unpublish_group + poll_and_user
+ end
+ end
+ end
+ end
+ unpublish_group = unpublish_group.uniq
+ if unpublish_group.size > 0
+ course_groups = CourseGroup.by_group_ids(unpublish_group)
+ else
+ course_groups = []
+ end
+ course_groups
+ end
+
+ def check_poll_setting_status
+ publish_course = params[:publish_time_groups]
+ unified_setting = params[:unified_setting]
+ if @course.is_end
+ normal_status(-1,"课堂已结束不能再修改!")
+ elsif unified_setting
+ poll_group_settings = @poll.poll_group_settings
+ if poll_group_settings.present?
+ p_time_present = poll_group_settings.publish_time_present.map(&:publish_time).min
+ if p_time_present < Time.now
+ normal_status(-1,"设置失败,存在已发布的分班!")
+ end
+ elsif params[:publish_time].blank?
+ normal_status(-1,"发布时间不允许为空")
+ end
+ elsif unified_setting.present? && !unified_setting #非统一设置,分班不能为空
+ if publish_course.present?
+ course_ids = publish_course.map{|a| a[:course_group_id]}.sum
+ publish_t = publish_course.map{|a| a[:publish_time]}
+ if course_ids.include?(nil) || course_ids.count == 0
+ normal_status(-1,"请选择分班!")
+ elsif publish_t.include?(nil) || publish_t.count == 0
+ normal_status(-1,"发布时间不允许为空")
+ end
+ else
+ normal_status(-1,"请选择分班!")
+ end
+ # elsif (@poll.poll_status != 1) && (params[:publish_time].to_time != @poll.publish_time) && (@user_course_identity > Course::CREATOR)
+ # normal_status(-1,"已发布/已截止的不能修发布时间!") #课堂管理员和超级管理员才有权限
+ end
+ end
+
+ def get_left_banner_id
+ left_banner_content = @course.course_modules.search_by_module_type("poll")
+ if left_banner_content.present?
+ @left_banner_id = left_banner_content.first.id
+ @left_banner_name = left_banner_content.first.module_name
+ else
+ tip_exception(404)
+ end
+ end
+
+end
diff --git a/app/controllers/praise_tread_controller.rb b/app/controllers/praise_tread_controller.rb
new file mode 100644
index 000000000..bf814db77
--- /dev/null
+++ b/app/controllers/praise_tread_controller.rb
@@ -0,0 +1,46 @@
+class PraiseTreadController < ApplicationController
+ include MessagesHelper
+ before_action :require_login
+ before_action :validate_params, only: [:like, :unlike]
+ before_action :find_object
+
+ def like
+ begin
+ return normal_status(2, "你已点过赞了") if by_user_liked?(@obj, current_user)
+ @praise_tread = @obj.praise_treads.build(user_id: current_user.id)
+ @praise_tread.save!
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def unlike
+ begin
+ return normal_status(2, "你还没有点过赞噢") unless by_user_liked?(@obj, current_user)
+ @praise_treads = @obj.praise_treads.user_liker(current_user)
+ @praise_treads.last.destroy! if @praise_treads.present?
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ private
+ def find_object
+ begin
+ @obj = params[:object_type].strip.classify.constantize.find params[:object_id]
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ return
+ end
+ end
+
+ def validate_params
+ return normal_status(2, "缺少参数:object_id") if params[:object_id].blank?
+ return normal_status(2, "缺少参数:object_type") if params[:object_type].blank?
+ end
+end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
new file mode 100644
index 000000000..c9ed6c597
--- /dev/null
+++ b/app/controllers/projects_controller.rb
@@ -0,0 +1,10 @@
+class ProjectsController < ApplicationController
+ def search
+ query_params = { keyword: params[:keyword], category: 'manage' }
+ projects = Users::ProjectService.new(current_user, query_params).call
+
+ params[:limit] = params[:per_page].to_i.zero? ? 20 : params[:per_page].to_i
+ @count = projects.count
+ @projects = paginate projects
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/question_banks_controller.rb b/app/controllers/question_banks_controller.rb
new file mode 100644
index 000000000..f814822e8
--- /dev/null
+++ b/app/controllers/question_banks_controller.rb
@@ -0,0 +1,287 @@
+class QuestionBanksController < ApplicationController
+ before_action :require_login
+ before_action :params_filter
+
+ # 题库选用列表
+ # object_type: # normal 普通作业题库; group 分组作业题库; poll问卷题库; exercise试卷题库; gtask 毕设选题题库;gtopic 毕设任务
+ # filter: 过滤条件 public公共题库, myself我的题库
+ # search: 搜索条件
+ def bank_list
+ page = params[:page] || 1
+ limit = params[:limit] || 15
+ @certification_teacher = current_user.is_certification_teacher || current_user.admin?
+ @objects = @object_type.classify.constantize.where(@object_filter)
+ @objects =
+ if params[:search]
+ if params[:filter] == 'public'
+ # 已认证才能获取题库
+ if @certification_teacher
+ sql = %Q{
+ #{@objects.table_name}.is_public = 1 and concat(#{@objects.table_name}.name, course_lists.name) like
+ '%#{params[:search]}%'
+ }
+ @objects.joins(:course_list).where(sql)
+ else
+ @objects.none
+ end
+ else
+ sql = %Q{
+ #{@objects.table_name}.user_id = #{current_user.id} and concat(#{@objects.table_name}.name, course_lists.name) like
+ '%#{params[:search]}%'
+ }
+ @objects.joins(:course_list).where(sql)
+ end
+ else
+ if params[:filter] == 'public'
+ if @certification_teacher # 只用平台已认证的老师才能获取题库
+ @objects.is_public
+ else
+ @objects.none
+ end
+ else
+ @objects.myself(current_user.id)
+ end
+ end
+ @objects = @objects.page(page).per(limit)
+ end
+
+ # 保存题库
+ def save_banks
+ tip_exception("bank_id不能为空") if params[:bank_id].blank?
+ tip_exception("一次最多只能选用15份题库") if params[:bank_id].count > 15
+ tip_exception("course_id不能为空") if params[:course_id].blank?
+ begin
+ objects = @object_type.classify.constantize.where(id: params[:bank_id])
+ course = Course.find params[:course_id]
+ new_object_ids = []
+ objects.each do |object|
+ case @object_type
+ when 'HomeworkBank' # 作业
+ new_object = quote_homework_bank object,course
+ when 'ExerciseBank'
+ if object.container_type == 'Exercise' # 试卷
+ new_object = quote_exercise_bank object, course
+ else # 问卷
+ new_object = quote_poll_bank object, course
+ end
+ when 'GtaskBank'
+ new_object = quote_gtask_bank object, course
+ when 'GtopicBank'
+ new_object = quote_gtopic_bank object, course
+ end
+ new_object_ids << new_object.id
+ end
+ render json: {status: 0, message: "选题成功", object_ids: new_object_ids}
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("题库选用失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def destroy
+ bank = current_bank
+
+ unless user.admin? || bank.user_id == user.id
+ render_forbidden
+ return
+ end
+
+ bank.destroy!
+
+ render_ok
+ end
+
+ def public
+ current_bank.update!(is_public: true)
+ render_ok
+ end
+
+ private
+
+ def current_bank
+ @_current_bank ||= @object_type.classify.constantize.where(@object_filter).find(params[:id])
+ end
+ def params_filter
+ type = ["normal", "group", "poll", "exercise", "gtask", "gtopic"]
+ tip_exception("object_type类型不正确") unless type.include?(params[:object_type])
+ # HomeworkBank 普通、分组作业题库;ExerciseBank试卷、问卷题库;GtaskBank毕设选题题库;GtopicBank毕设任务题库;
+ case params[:object_type]
+ when 'normal'
+ @object_type = "HomeworkBank"
+ @object_filter = "homework_type = 1" # 普通作业
+ when 'group'
+ @object_type = "HomeworkBank"
+ @object_filter = "homework_type = 3" # 分组作业
+ when 'poll'
+ @object_type = "ExerciseBank"
+ @object_filter = "container_type = 'Poll'" # 问卷
+ when 'exercise'
+ @object_type = "ExerciseBank"
+ @object_filter = "container_type = 'Exercise'" # 试卷
+ when 'gtask'
+ @object_type = "GtaskBank"
+ @object_filter = nil
+ when 'gtopic'
+ @object_type = "GtopicBank"
+ @object_filter = nil
+ end
+ end
+
+ def quote_homework_bank homework, course
+ ActiveRecord::Base.transaction do
+ # 复制作业的基本信息
+ new_homework = HomeworkCommon.new(name: homework.name, user_id: current_user.id, description: homework.description,
+ homework_type: homework.homework_type, course_id: course.id, homework_bank_id: homework.id,
+ reference_answer: homework.reference_answer)
+
+ # 作业的基本设置复制
+ new_homework.homework_detail_manual = HomeworkDetailManual.new
+ new_homework_detail_manual = new_homework.homework_detail_manual
+
+ if new_homework.homework_type == "group"
+ # 分组作业表的复制
+ new_homework.homework_detail_group = HomeworkDetailGroup.new
+ new_homework.homework_detail_group.min_num = homework.min_num
+ new_homework.homework_detail_group.max_num = homework.max_num
+ new_homework.homework_detail_group.base_on_project = homework.base_on_project
+ end
+ # 附件
+ homework.attachments.try(:each) do |attachment|
+ att = attachment.copy
+ att.container_id = nil
+ att.container_type = nil
+ att.author_id = homework.user_id
+ att.copy_from = attachment.id
+ att.save
+ new_homework.attachments << att
+ end
+
+ if new_homework.save
+ new_homework_detail_manual.save if new_homework_detail_manual
+ new_homework.homework_detail_group.save if new_homework.homework_detail_group
+ HomeworksService.new.create_works_list(new_homework, course)
+ homework.update_column(:quotes, homework.quotes+1)
+ end
+ new_homework
+ end
+ end
+
+ def quote_exercise_bank exercise, course
+ ActiveRecord::Base.transaction do
+ new_exercise = Exercise.new(:exercise_name => exercise.name, :exercise_description => exercise.description,
+ :user_id => current_user.id, :course_id => course.id, :exercise_bank_id => exercise.id)
+ # 复制试卷基本信息
+ exercise.exercise_bank_questions.each do |q|
+ option = {
+ :question_title => q.question_title,
+ :question_type => q.question_type || 1,
+ :question_number => q.question_number,
+ :question_score => q.question_score,
+ :shixun_id => q.shixun_id
+ }
+ exercise_question = new_exercise.exercise_questions.new option
+ # question_type:5实训题;其他是非实训题
+ if q.question_type != 5
+ # 复制选择题题目选项
+ q.exercise_bank_choices.try(:each_with_index) do |choice, index|
+ exercise_question.exercise_choices.new({choice_position: index+1, choice_text: choice.choice_text})
+ end
+
+ # 复制标准答案(填空题和问答题) 多空填空题的话,应该是原标准答案的exercise_choice_id,即为题空的位置。
+ q.exercise_bank_standard_answers.try(:each) do |answer|
+ exercise_question.exercise_standard_answers.new({exercise_choice_id: answer.exercise_bank_choice_id, answer_text: answer.answer_text})
+ end
+ else
+ # 复制实训题
+ q.exercise_bank_shixun_challenges.try(:each_with_index) do |sc, index|
+ exercise_question.exercise_shixun_challenges.new({position: index+1, challenge_id: sc.challenge_id,
+ shixun_id: sc.shixun_id, question_score: sc.question_score})
+ end
+ end
+ end
+ # 添加学生
+ # if new_exercise.save
+ # new_exercise.create_exercise_list
+ # exercise.update_column(:quotes, exercise.quotes+1)
+ # end
+ new_exercise if new_exercise.save!
+ end
+ end
+
+ def quote_poll_bank poll, course
+ ActiveRecord::Base.transaction do
+ new_poll = Poll.new(:polls_name => poll.name, :polls_description => poll.description, :user_id => current_user.id,
+ :polls_type => 'Course', :course_id => course.id, :exercise_bank_id => poll.id)
+
+ poll.exercise_bank_questions.try(:each) do |q|
+ option = {
+ :question_title => q.question_title,
+ :question_type => q.question_type || 1,
+ :is_necessary => q.is_necessary,
+ :question_number => q.question_number,
+ :max_choices => q.max_choices,
+ :min_choices => q.min_choices
+ }
+ poll_question = new_poll.poll_questions.new option
+ q.exercise_bank_choices.try(:each_with_index) do |choice, index|
+ poll_question.poll_answers.new({answer_position: index+1, answer_text: choice.choice_text})
+ end
+ end
+ # if new_poll.save
+ # new_poll.create_polls_list
+ # poll.update_column(:quotes, poll.quotes+1)
+ # end
+ new_poll if new_poll.save!
+ end
+ end
+
+ def quote_gtask_bank task, course
+ ActiveRecord::Base.transaction do
+ new_task = GraduationTask.new
+ new_task.attributes = task.attributes.dup.except("id", "course_id", "user_id", "quotes", "graduation_task_id",
+ "course_list_id")
+ new_task.course_id = course.id
+ new_task.user_id = current_user.id
+ if new_task.save!
+ new_task.create_work_list
+ end
+ # 复制附件内容
+ task.attachments.try(:each) do |attachment|
+ att = attachment.copy
+ att.container_id = nil
+ att.container_type = nil
+ att.author_id = task.user_id
+ att.copy_from = attachment.id
+ att.save
+ new_task.attachments << att
+ end
+ task.update_column(:quotes, task.quotes+1)
+ new_task
+ end
+ end
+
+ def quote_gtopic_bank topic, course
+ ActiveRecord::Base.transaction do
+ new_topic = GraduationTopic.new
+ new_topic.attributes = topic.attributes.dup.except("id", "course_id", "user_id", "graduation_topic_id", "course_list_id")
+ new_topic.course_id = course.id
+ new_topic.user_id = current_user.id
+ new_topic.save
+
+ topic.attachments.each.try(:each) do |attachment|
+ att = attachment.copy
+ att.container_id = nil
+ att.container_type = nil
+ att.author_id = topic.user_id
+ att.copy_from = attachment.id
+ att.save
+ new_topic.attachments << att
+ end
+ topic.update_column(:quotes, topic.quotes+1)
+ new_topic
+ end
+ end
+
+
+end
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
new file mode 100644
index 000000000..25c268bc7
--- /dev/null
+++ b/app/controllers/repositories_controller.rb
@@ -0,0 +1,16 @@
+class RepositoriesController < ApplicationController
+ before_action :git_base
+
+ def show
+ repo_path = Shixun.find_by_identifier(params[:identifier]).try(:repo_name)
+ {repo_path: repo_path, path: params[:path]}
+ @entries = @git.file_tree(params)
+ end
+
+ private
+
+ def git_base
+ @shixun = Shixun.find_by_identifier(params[:shixun_identifier])
+ @repo_namespace = @shixun.repo_path
+ end
+end
diff --git a/app/controllers/schools_controller.rb b/app/controllers/schools_controller.rb
new file mode 100644
index 000000000..1551091aa
--- /dev/null
+++ b/app/controllers/schools_controller.rb
@@ -0,0 +1,8 @@
+class SchoolsController < ApplicationController
+
+
+ def school_list
+ q = params[:search] ? params[:search].strip : ""
+ @schools = School.where("name like ?", "%#{q}%").pluck(:name)
+ end
+end
diff --git a/app/controllers/shixuns_controller.rb b/app/controllers/shixuns_controller.rb
new file mode 100644
index 000000000..00ae559b1
--- /dev/null
+++ b/app/controllers/shixuns_controller.rb
@@ -0,0 +1,746 @@
+class ShixunsController < ApplicationController
+ before_action :require_login, except: [:download_file, :index]
+ # before_action :check_auth, except: [:download_file, :index]
+
+ before_action :find_shixun, except: [:index, :new, :create, :menus, :get_recommend_shixuns, :propaedeutics,
+ :departments, :apply_shixun_mirror, :get_mirror_script, :download_file]
+ before_action :find_repo_name, only: [:repository, :commits, :file_content, :update_file, :shixun_exec, :copy]
+
+ before_action :allowed, only: [:update, :close, :update_propaedeutics, :settings, :publish,
+ :shixun_members_added, :change_manager, :collaborators_delete,
+ :cancel_publish, :add_collaborators]
+ before_action :portion_allowed, only: [:copy]
+
+ before_action :special_allowed, only: [:send_to_course, :search_user_courses]
+
+ include ShixunsHelper
+ include ApplicationHelper
+
+ ## 获取课程列表
+ def index
+ ## 我的实训
+ @shixuns =
+ if params[:order_by] == 'mine'
+ current_user.my_shixuns
+ else
+ Shixun.unhidden
+ end
+
+ ## 方向
+ if params[:tag_level].present? && params[:tag_id].present?
+ @shixuns = @shixuns.filter_tag(params[:tag_level].to_i, params[:tag_id].to_i)
+ case params[:tag_level].to_i
+ when 1 #大类
+ @search_tags = Repertoire.find(params[:tag_id].to_i).name
+ when 2 #子类
+ @search_tags = SubRepertoire.find(params[:tag_id].to_i).name
+ when 3 #tag
+ tag = TagRepertoire.find(params[:tag_id].to_i)
+ @search_tags = "#{tag.sub_repertoire.name} / #{tag.name}"
+ end
+ end
+
+ ## 搜索关键字 匹配关卡名, 用户名, 实训名 和 空格多搜索
+ if params[:keyword].present?
+ keyword = params[:keyword].strip
+ @shixuns = @shixuns.joins(:users, challenges: :challenge_tags).
+ where("challenge_tags.name like '%#{keyword}%'
+ or challenges.subject like '%#{keyword}%'
+ or concat(lastname, firstname) like '%#{keyword}%'
+ or shixuns.name like '%#{keyword.split(" ").join("%")}%'")
+ end
+
+ ## 筛选 状态
+ if params[:status].present? && params[:status].to_i != 0
+ params[:status] = [0, 1] if params[:status].to_i == 1
+ @shixuns = @shixuns.where(status: params[:status])
+ end
+
+ ## 筛选 难度
+ if params[:diff].present? && params[:diff].to_i != 0
+ @shixuns = @shixuns.where(trainee: params[:diff])
+ end
+
+ ## 排序参数
+ bsort = params[:sort] || 'desc'
+ case params[:order_by] || 'publish_time'
+ when 'new'
+ @shixuns = @shixuns.order("shixuns.status = 2 desc, shixuns.created_at #{bsort}")
+ when 'hot'
+ @shixuns = @shixuns.order("shixuns.status = 2 desc, myshixuns_count #{bsort}")
+ when 'mine'
+ @shixuns = @shixuns.order("shixuns.created_at #{bsort}")
+ else
+ @shixuns = @shixuns.order("shixuns.status = 2 desc, publish_time #{bsort}")
+ end
+
+
+ @total_count = @shixuns.count
+
+ ## 分页参数
+ page = params[:page] || 1
+ limit = params[:limit] || 16
+
+ @shixuns = @shixuns.includes(:tag_repertoires, :challenges).page(page).per(limit)
+ end
+
+ ## 获取顶部菜单
+ def menus
+ @repertoires = Repertoire.includes(sub_repertoires: [:tag_repertoires]).order("updated_at asc")
+ # respond_with @repertoires
+
+ render_json
+ end
+
+ ## 实训详情
+ def show
+ # 当前用户开启的实训
+ can_fork = current_user.is_certification_teacher || current_user.admin?
+ unless can_fork
+ @can_fork = {can_fork: "已经职业认证的教师才能fork实训",
+ certi_url: edu_setting('old_edu_host') + "/account/professional_certification"}
+ end
+ @current_myshixun = @shixun.current_myshixun(current_user)
+ if @shixun.fork_from
+ fork_shixun = Shixun.select(:id, :user_id, :name, :identifier).where(id: @shixun.fork_from).first
+ @fork_from = {name: fork_shixun.name, username: fork_shixun.owner.try(:full_name),
+ fork_identifier: fork_shixun.identifier} if fork_shixun
+ end
+ @power = current_user.manager_of_shixun?(@shixun)
+ # 更新是为了首页的排序,myshixun的动静需要在实训中展现出来
+ if @current_myshixun && params[:exit] && !current_user.admin?
+ @current_myshixun.update_column(:updated_at, Time.now)
+ end
+ end
+
+ def show_right
+ owner = @shixun.owner
+ #@fans_count = owner.followers.count
+ #@followed_count = owner.followed_users.count
+ @user_own_shixuns = owner.shixuns.published.count
+ end
+
+ # 排行榜
+ def ranking_list
+ if @shixun.status == 2
+ sql = "
+ select m.user_id, u.login, u.lastname, m.updated_at,
+ (select sum(cost_time) from games g where g.myshixun_id = m.id) as time,
+ (select sum(final_score) from games g where g.myshixun_id = m.id) as score
+ from (myshixuns m join users u on m.user_id = u.id) where m.shixun_id = #{@shixun.id} and m.status = 1
+ order by score desc, time asc limit 10
+ "
+ @myshixuns = Myshixun.find_by_sql(sql)
+
+ end
+ end
+
+ #评论
+ def discusses
+ new_params = params.merge(container_id: @shixun.id, container_type: 'Shixun')
+ discusses = ShixunsService.new.shixun_discuss new_params, current_user
+
+ if discusses.present?
+ @children_list = discusses[:children_list]
+ else
+ @children_list = []
+ end
+ end
+
+ def copy
+ ActiveRecord::Base.transaction do
+ begin
+ @new_shixun = Shixun.new
+ @new_shixun.attributes = @shixun.attributes.dup.except("id","user_id","visits","gpid","status", "identifier", "averge_star",
+ "homepage_show","repo_name", "myshixuns_count", "challenges_count",
+ "can_copy")
+ @new_shixun.user_id = User.current.id
+ @new_shixun.averge_star = 5
+ @new_shixun.identifier = generate_identifier Shixun, 8
+ @new_shixun.fork_from = @shixun.id
+ @new_shixun.save!
+
+ # 同步shixun_info的信息
+ if @shixun.shixun_info.present?
+ ShixunInfo.create!(shixun_id: @new_shixun.id,
+ description: @shixun.description,
+ evaluate_script: @shixun.evaluate_script)
+ end
+
+ # 同步镜像
+ if @shixun.mirror_repositories.present?
+ @shixun.mirror_repositories.each do |mirror|
+ ShixunMirrorRepository.create!(:shixun_id => @new_shixun.id, :mirror_repository_id => mirror.id)
+ end
+ end
+ # 同步技术标签
+ @shixun.shixun_tag_repertoires.each do |str|
+ ShixunTagRepertoire.create!(:tag_repertoire_id => str.tag_repertoire_id, :shixun_id => @new_shixun.id)
+ end
+
+ # fork版本库
+ logger.info("###########fork_repo_path: ######{@repo_path}")
+ project_fork(@new_shixun, @repo_path, current_user.login)
+
+ ShixunMember.create!(:user_id => User.current.id, :shixun_id => @new_shixun.try(:id), :role => 1)
+
+ # 同步复制关卡
+ if @shixun.challenges.present?
+ @shixun.challenges.each do |challenge|
+ new_challenge = Challenge.new
+ new_challenge.attributes = challenge.attributes.dup.except("id","shixun_id","user_id", "challenge_tags_count")
+ new_challenge.user_id = User.current.id
+ new_challenge.shixun_id = @new_shixun.id
+ new_challenge.save!
+ if challenge.st == 0 # 评测题
+ # 同步测试集
+ if challenge.test_sets.present?
+ challenge.test_sets.each do |test_set|
+ new_test_set = TestSet.new
+ new_test_set.attributes = test_set.attributes.dup.except("id","challenge_id")
+ new_test_set.challenge_id = new_challenge.id
+ new_test_set.save!
+ end
+ end
+ # 同步关卡标签
+ challenge_tags = ChallengeTag.where("challenge_id =? and challenge_choose_id is null", challenge.id)
+ if challenge_tags.present?
+ challenge_tags.each do |challenge_tag|
+ ChallengeTag.create!(:challenge_id => new_challenge.id, :name => challenge_tag.try(:name))
+ end
+ end
+ elsif challenge.st == 1 # 选择题
+ if challenge.challenge_chooses.present?
+ challenge.challenge_chooses.each do |challenge_choose|
+ new_challenge_choose = ChallengeChoose.new
+ new_challenge_choose.attributes = challenge_choose.attributes.dup.except("id","challenge_id")
+ new_challenge_choose.challenge_id = new_challenge.id
+ new_challenge_choose.save!
+ # 每一题的选项
+ if challenge_choose.challenge_questions.present?
+ challenge_choose.challenge_questions.each do |challenge_question|
+ new_challenge_question = ChallengeQuestion.new
+ new_challenge_question.attributes = challenge_question.attributes.dup.except("id","challenge_choose_id")
+ new_challenge_question.challenge_choose_id = new_challenge_choose.id
+ new_challenge_question.save!
+ end
+ end
+ # 每一题的知识标签
+ st_challenge_tags = ChallengeTag.where(:challenge_id => challenge.id, :challenge_choose_id => challenge_choose.id)
+ if st_challenge_tags.present?
+ st_challenge_tags.each do |st_challenge_tag|
+ ChallengeTag.create!(:challenge_id => new_challenge.id, :name => st_challenge_tag.try(:name), :challenge_choose_id => new_challenge_choose.id)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ rescue Exception => e
+ uid_logger_error("copy shixun failed ##{e.message}")
+ g.delete_project(gshixungshixun.id) if gshixun.try(:id).present? # 异常后,如果已经创建了版本库需要删除该版本库
+ tip_exception("实训Fork失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #合作者
+ def collaborators
+ @user = current_user
+ @members = @shixun.shixun_members.includes(:user)
+ end
+
+ def fork_list
+ @shixuns = Shixun.where(:fork_from => @shixun.id)
+ @shixuns_count = @shixuns.count
+
+ ## 分页参数
+ page = params[:page] || 1
+ limit = params[:limit] || 20
+
+ @shixuns = @shixuns.page(page).per(limit)
+ end
+
+ def new
+ @introduction_sample = PlatformSample.where(samples_type: ['introduction', 'knowledge']).pluck([:samples_type, :contents])
+ @main_type = shixun_main_type
+ @small_type = shixun_small_type
+ end
+
+ # 注意这里传参都应该使用params[:shixun]['name']这种格式
+ def create
+ # 评测脚本的一些操作
+ main_type, sub_type = params[:main_type], params[:small_type]
+ mirror = MirrorScript.where(:mirror_repository_id => main_type)
+
+ identifier = generate_identifier Shixun, 8
+ @shixun = Shixun.new(shixun_params)
+ @shixun.identifier = identifier
+ @shixun.user_id = current_user.id
+ @shixun.reset_time, @shixun.modify_time = Time.now, Time.now
+
+ if sub_type.blank?
+ shixun_script = mirror.first.try(:script)
+ else
+ main_mirror = MirrorRepository.find(main_type).type_name
+ sub_mirror = MirrorRepository.where(id: sub_type).pluck(:type_name)
+ if main_mirror == "Java" && sub_mirror.include?("Mysql")
+ shixun_script = mirror.last.try(:script)
+ else
+ shixun_script = mirror.first.try(:script)
+ shixun_script = modify_shixun_script @shixun, shixun_script
+ end
+ end
+
+ ActiveRecord::Base.transaction do
+ begin
+ @shixun.save!
+ # shixun_info关联ß
+ ShixunInfo.create!(shixun_id: @shixun.id, evaluate_script: shixun_script, description: params[:description])
+ # 实训的公开范围
+ if params[:scope_partment].present?
+ arr = []
+ ids = School.where(:name => params[:scope_partment]).pluck(:id).uniq
+ ids.each do |id|
+ arr << { :school_id => id, :shixun_id => @shixun.id }
+ end
+ ShixunSchool.create!(arr)
+ end
+
+ # 实训合作者
+ @shixun.shixun_members.create!(user_id: current_user.id, role: 1)
+
+ # 镜像-实训关联表
+ ShixunMirrorRepository.create!(:shixun_id => @shixun.id, :mirror_repository_id => main_type.to_i) if main_type.present?
+ if sub_type.present?
+ sub_type.each do |mirror|
+ ShixunMirrorRepository.create!(:shixun_id => @shixun.id, :mirror_repository_id => mirror)
+ end
+ end
+
+ # 创建版本库
+ repo_path = repo_namespace(User.current.login, @shixun.identifier)
+ GitService.add_repository(repo_path: repo_path)
+ # todo: 为什么保存的时候要去除后面的.git呢??
+ @shixun.update_column(:repo_name, repo_path.split(".")[0])
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("实训创建失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def apply_shixun_mirror
+ form_params = params.permit(*%i[language runtime run_method attachment_id])
+ form = ApplyShixunMirrorForm.new(form_params)
+ form.validate!
+
+ tiding = Tiding.new(
+ user_id: 1,
+ trigger_user_id: current_user.id,
+ container_type: 'SendMessage',
+ viewed: 0,
+ tiding_type: 'Apply',
+ extra: form.to_json
+ )
+ ActiveRecord::Base.transaction do
+ # TODO: 由于tiding是多态,而SendMessage却没有对应的model,因此,如果不跳过验证会报 container must exist的错.
+ tiding.save(validate: false)
+
+ form.attachment.update!(container: tiding)
+ end
+
+ sucess_status
+ rescue ActiveModel::ValidationError => ex
+ tip_exception(ex.message)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("申请失败")
+ end
+
+ def update
+ h = {test_set_permission: params[:test_set_permission], code_hidden: params[:code_hidden],
+ task_pass: params[:task_pass], hide_code: params[:hide_code], forbid_copy: params[:forbid_copy]}
+
+ s_params = shixun_params.merge(h)
+
+ ActiveRecord::Base.transaction do
+ begin
+ @shixun.shixun_mirror_repositories.destroy_all
+ if params[:main_type].present?
+ ShixunMirrorRepository.create(:shixun_id => @shixun.id, :mirror_repository_id => params[:main_type].to_i)
+ end
+ if params[:small_type].present?
+ params[:small_type].each do |mirror|
+ ShixunMirrorRepository.create(:shixun_id => @shixun.id, :mirror_repository_id => mirror)
+ end
+ end
+
+ @shixun.update_attributes(s_params)
+ @shixun.shixun_info.update_attributes(description: params[:description], evaluate_script: params[:evaluate_script])
+ @shixun.shixun_schools.delete_all
+ if params[:scope_partment].present? && params[:user_scope].to_i == 1
+ arr = []
+ ids = School.where(:name => params[:scope_partment]).pluck(:id).uniq
+ ids.each do |id|
+ arr << { :school_id => id, :shixun_id => @shixun.id }
+ end
+ ShixunSchool.create!(arr)
+ use_scope = 1
+ else
+ use_scope = 0
+ end
+ @shixun.update_attributes!(:use_scope => use_scope)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("实训保存失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 永久关闭实训
+ def close
+ @shixun.update_attributes(status: 3, closer_id: current_user.id, end_time: Time.now)
+ sucess_status
+ end
+
+ def propaedeutics
+ @content = Shixun.where(identifier: params[:identifier]).pluck(:propaedeutics)
+ end
+
+ # 更新背景知识
+ def update_propaedeutics
+ @shixun.update_column(:propaedeutics, params[:content])
+ end
+
+ # 获取推荐实训接口 2个热门实训 + 2个最新实训
+ def get_recommend_shixuns
+ hot_shixuns = Shixun.field_for_recommend.published.order("myshixuns_count desc").limit(2)
+ newest_shixuns = Shixun.field_for_recommend.published.order("created_at desc").limit(2)
+ @recommend_shixuns = hot_shixuns + newest_shixuns
+ end
+
+ def settings
+ @choice_main_type = @shixun.main_mirror_id
+ @choice_small_type = @shixun.small_mirror_id
+ @main_type = shixun_main_type
+ @small_type = shixun_small_type
+ #@mirror_script = MirrorScript.select([:id, :script_type]).find(@shixun.mirror_script_id).attributes if @shixun.mirror_script_id && @shixun.mirror_script_id != 0
+ # @shixun_main_mirror = @shixun.show_shixun_mirror
+ # @script_type = @shixun.script_tag.try(:script_type) || "无"
+ # @evaluate_scirpt = @shixun.evaluate_script || "无"
+ end
+
+ # 获取脚本内容
+ def get_script_contents
+ mirrir_script = MirrorScript.find(params[:script_id])
+ script = mirrir_script.try(:script)
+ @description = mirrir_script.try(:description)
+ @script = modify_shixun_script @shixun, script
+ end
+
+ def get_custom_script
+ shixun_script = PlatformSample.where(:samples_type => "script").first.try(:contents)
+ compile = params[:compile]
+ execute = params[:executive]
+ if shixun_script
+ shixun_script = compile.blank? ? shixun_script.gsub("COMPILEFUNCTION", "").gsub("CHALLENGEFIELPATH", "") :
+ shixun_script.gsub("COMPILEFUNCTION", "#{compile_command}").gsub("COMPILECOMMAND", "#{compile}")
+ shixun_script = execute.blank? ? shixun_script.gsub("EXECUTEFUNCTION", "") : shixun_script.gsub("EXECUTECOMMAND", "#{execute}")
+ shixun_script = modify_shixun_script @shixun, shixun_script
+ end
+ @shixun_script = shixun_script
+ end
+
+ def departments
+ @scope = []
+ q = params[:q]
+ if q && q.strip.present?
+ @scope = School.where("name like ?", "%#{q.strip}%").pluck(:name)
+ end
+
+ end
+
+ def get_mirror_script
+ mirror = MirrorRepository.find(params[:mirror_id])
+ @script = mirror.mirror_scripts
+ end
+
+ # TODO: 目前实训只做软删除.
+ def destroy
+ # apply_records = ApplyAction.where(container_id: @shixun.id, container_type: "ApplyShixun")
+ # apply_records.delete_all if apply_records
+ # HomeworkCommonShixuns.where(shixun_id: @shixun).delete_all
+ # @shixun.destroy
+ @shixun.update_column(:status, -1)
+ end
+
+ # 开启挑战
+ # 以前在开启挑战的时候检测实训是否更新,更新则重置,觉得应该放在TPI更好
+ # 中间需要一个过渡动画
+ # TODO: 第一次开启实训都会去判断是否是纯选择题类型,感觉做成在创建关卡的时候就判断该实训是否是纯选择题更加合适
+ def shixun_exec
+ current_myshixun = @shixun.current_myshixun(current_user.id)
+
+ if @shixun.opening_time.present? && @shixun.opening_time > Time.now && current_user.shixun_identity(@shixun) > User::EDU_SHIXUN_MEMBER
+ tip_show_exception(-3, "#{@shixun.opening_time.strftime('%Y-%m-%d %H:%M:%S')}")
+ end
+
+ if current_myshixun
+ # 如果TPM和TPI的管卡数不相等或者关卡顺序错了,说明实训被极大的改动,需要重置
+ if current_myshixun.games.count != @shixun.challenges_count || current_myshixun.games.map(&:challenge_id).sort != Challenge.where(shixun_id: @shixun.id).pluck(:id).sort
+ # 这里页面弹框要收到 当前用户myshixun的identifier.
+ tip_show_exception("/myshixuns/#{current_myshixun.try(:identifier)}/reset_my_game")
+ end
+
+ # 如果存在实训,则直接进入实训
+ @current_task = current_myshixun.current_task
+ else
+ # 如果未创建关卡一定不能开启实训,否则TPI没法找到当前的关卡
+ if @shixun.challenges_count == 0
+ tip_exception("开启实战前请先创建实训关卡")
+ end
+
+ # 判断实训是否全为选择题
+ is_choice_type = @shixun.is_choice_type?
+ if !is_choice_type
+ commit = GitService.commits(repo_path: @repo_path).try(:first)
+ Rails.logger.info("First comit########{commit}")
+ tip_exception("开启实战前请先在版本库中提交代码") if commit.blank?
+ commit_id = commit["id"]
+ end
+
+ ActiveRecord::Base.transaction do
+ begin
+ cloud_bridge = edu_setting('cloud_bridge')
+ myshixun_identifier = generate_identifier Myshixun, 10
+ myshixun = @shixun.myshixuns.create!(user_id: current_user.id, identifier: myshixun_identifier,
+ modify_time: @shixun.modify_time, reset_time: @shixun.reset_time,
+ onclick_time: Time.now, commit_id: commit_id)
+ uid_logger("myshixun_id is #{myshixun.id}")
+
+ # 如果实训是纯选择题,则不需要去fork仓库以及中间层的相关操作了
+ unless is_choice_type
+ # fork仓库
+ project_fork(myshixun, @repo_path, current_user.login)
+
+ rep_url = Base64.urlsafe_encode64(repo_ip_url @repo_path )
+ uid_logger("start openGameInstance")
+ uri = "#{cloud_bridge}/bridge/game/openGameInstance"
+ logger.info("end openGameInstance")
+ params = {tpiID: "#{myshixun.id}", tpmGitURL:rep_url, tpiRepoName: myshixun.repo_name.split("/").last}
+ uid_logger("openGameInstance params is #{params}")
+ # res = interface_post uri, params, 83, "实训云平台繁忙(繁忙等级:83)"
+ end
+ # 其它创建关卡等操作
+ challenges = @shixun.challenges
+ # 之所以增加user_id是为了方便统计查询性能
+ challenges.each_with_index do |challenge, index|
+ status = (index == 0 ? 0 : 3)
+ game_identifier = generate_identifier(Game, 12)
+ Game.create!(:challenge_id => challenge.id, :myshixun_id => myshixun.id, :status => status, :user_id => myshixun.user_id,
+ :open_time => Time.now, :identifier => game_identifier, :modify_time => challenge.modify_time)
+ end
+
+ # REDO:开启实训时更新关联作品的状态
+ HomeworksService.new.update_myshixun_work_status myshixun
+
+ @current_task = myshixun.current_task
+ uid_logger("## shixun exec: myshixun id is #{myshixun.id}")
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("实训云平台繁忙(繁忙等级:81)")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+
+
+ end
+ # gameID 及实训ID
+ # status: 0 , 1 申请过, 2,实训关卡路径未填, 3 实训标签未填, 4 实训未创建关卡
+ def publish
+ @status = 0
+ @position = []
+ begin
+ if @shixun.challenges.count == 0
+ @status = 4
+ else
+ @shixun.challenges.each do |challenge|
+ if challenge.challenge_tags.count == 0
+ @status = 3
+ @position << challenge.position
+ end
+ end
+ unfinish_challenge = @shixun.challenges.where(:st => 0, :path => nil)
+ if unfinish_challenge.count > 0 && !@shixun.is_choice_type?
+ @status = 2
+ @pos = []
+ unfinish_challenge.each do |challenge|
+ @pos << challenge.position
+ end
+ end
+ end
+ if @status == 0
+ @shixun.update_attributes!(:status => 1)
+ apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first
+ if apply && apply.status == 0
+ @status = 0
+ else
+ ApplyAction.create(:container_type => "ApplyShixun", :container_id => @shixun.id, :user_id => current_user.id, :status => 0)
+ #begin
+ # status = Trustie::Sms.send(mobile: '18711011226', send_type:'publish_shixun' , name: '管理员')
+ #rescue => e
+ # Rails.logger.error "发送验证码出错: #{e}"
+ #end
+ @status = 1
+ end
+ end
+ rescue Exception => e
+ logger.error("pushlish game #{e}")
+ end
+ end
+
+ include GitCommon
+
+ def update_file
+ content = params[:content]
+ author_name = current_user.full_name
+ author_email = current_user.mail
+ @content = update_file_content content, @repo_path, @path, author_email, author_name, "Edit by browser"
+ end
+
+ def add_collaborators
+ raise("搜索内容不能为空") unless params[:search]
+ member_ids = "(" + @shixun.shixun_members.map(&:user_id).join(',') + ")"
+ condition = "%#{params[:search].strip}%".gsub(" ","")
+ @users = User.where("id not in #{member_ids} and status = 1 and LOWER(concat(lastname, firstname, login, mail, nickname)) LIKE '#{condition}'")
+ end
+
+ def shixun_members_added
+ raise("user_ids 不能为空!") if params[:user_ids].blank?
+ memberships = params[:user_ids]
+ memberships.each do |member|
+ ShixunMember.create!(:user_id => member, :shixun_id => @shixun.id, :role => 2)
+ end
+ end
+
+ def change_manager
+ # 搜索成员
+ if request.get?
+ @collaborators = @shixun.shixun_members.where("user_id != #{@shixun.user_id}")
+ else
+ if params[:user_id]
+ man_member = ShixunMember.where(:shixun_id => @shixun.id, :user_id => @shixun.user_id).first
+ cha_member = ShixunMember.where(:user_id => params[:user_id], :shixun_id => @shixun.id).first
+ if man_member && cha_member
+ man_member.update_attribute(:role, 2)
+ cha_member.update_attribute(:role, 1)
+ @shixun.update_attribute(:user_id, cha_member.user_id)
+ end
+ end
+ end
+ end
+
+ # 刪除合作者
+ def collaborators_delete
+ raise("user_id不能为空") if params[:user_id].blank?
+ shixun_member = ShixunMember.where(:user_id => params[:user_id], :shixun_id => @shixun.id, :role => 2).first
+ shixun_member.delete
+ end
+
+ # 实训的发送至课堂:搜索课堂
+ def search_user_courses
+ ## 分页参数
+ page = params[:page] || 1
+ limit = params[:limit] || 20
+ if params[:search]
+ search = "%#{params[:search].to_s.strip.downcase}%"
+ course_ids = Course.find_by_sql("SELECT c.id FROM courses c, members m, member_roles mr
+ WHERE m.course_id = c.id AND m.id=mr.member_id AND mr.role_id in (3,7,9)
+ AND m.user_id=#{current_user.id} AND c.is_delete = 0 AND c.is_end = 0
+ AND c.name like '#{search}' ").map(&:id)
+
+ else
+ course_ids = Course.find_by_sql("SELECT c.id, c.name FROM courses c, members m, member_roles mr
+ WHERE m.course_id = c.id AND m.id=mr.member_id AND mr.role_id in (3,7,9)
+ AND m.user_id=#{current_user.id} AND c.is_delete = 0 AND c.is_end = 0").map(&:id)
+
+ end
+
+ @course_count = course_ids.length
+ @courses = Course.where(:id => course_ids).page(page).per(limit)
+ end
+
+ # 将实训发送到课程
+ def send_to_course
+ @course = Course.find(params[:course_id])
+ homework = HomeworksService.new.create_homework shixun, @course, nil, current_user
+ end
+
+ # 二维码扫描下载
+ def download_file
+ file_path = params[:file_name]
+ send_file "#{Rails.root}/#{file_path}", :filename => "#{file_path}",
+ :type => 'shixun',
+ :disposition => 'attachment' #inline can open in browser
+ end
+
+ # 撤销发布
+ def cancel_publish
+ apply = ApplyAction.where(:container_type => "ApplyShixun", :container_id => @shixun.id).order("created_at desc").first
+ if apply && apply.status == 0
+ apply.update_attribute(:status, 3)
+ apply.tidings.destroy_all
+ @shixun.update_column('status', 0)
+ end
+ end
+
+private
+ def shixun_params
+ raise("实训名称不能为空") if params[:name].blank?
+ params.require(:shixun).permit(:name, :trainee, :webssh, :can_copy, :use_scope, :vnc, :test_set_permission,
+ :task_pass, :repo_name, :multi_webssh, :opening_time, :mirror_script_id)
+ end
+
+ def find_shixun
+ @shixun = Shixun.find_by_identifier(params[:identifier])
+ shixun = Shixun.where(identifier: params[:identifier]).first
+ if @shixun.blank?
+ normal_status(404, "...")
+ return
+ end
+
+ if !current_user.shixun_permission(@shixun) || (@shixun.status == -1 && !current_user.admin?)
+ tip_exception(403, "..")
+ end
+ end
+
+ def find_repo_name
+ @repo_path = @shixun.try(:repo_path)
+ @path = params[:path]
+ end
+
+ def allowed
+ unless current_user.manager_of_shixun?(@shixun)
+ tip_exception(403, "..")
+ end
+ end
+
+ def portion_allowed
+ if current_user.shixun_identity(@shixun) > User::EDU_CERTIFICATION_TEACHER
+ raise Educoder::TipException.new(403, "..")
+ end
+ end
+
+ def special_allowed
+ if @shixun.status != 2
+ tip_exception(403, "..")
+ end
+ end
+end
diff --git a/app/controllers/stages_controller.rb b/app/controllers/stages_controller.rb
new file mode 100644
index 000000000..3d0087981
--- /dev/null
+++ b/app/controllers/stages_controller.rb
@@ -0,0 +1,117 @@
+class StagesController < ApplicationController
+ before_action :require_login
+ before_action :find_subject, only: [:create, :index]
+ before_action :find_stage, only: [:update, :destroy, :edit, :up_position, :down_position]
+ before_action :allowed, except: [:index]
+
+ def index
+ @user = current_user
+ @stages = @subject.stages
+ end
+
+ def create
+ ActiveRecord::Base.transaction do
+ begin
+ @stage = Stage.new(stage_params)
+ @stage.subject_id = @subject.id
+ @stage.user_id = current_user.id
+ @stage.position = @subject.stages.count + 1
+ @stage.save!
+ unless params[:shixun_id].blank?
+ shixuns = Shixun.where(id: params[:shixun_id])
+ shixuns.each do |shixun|
+ StageShixun.create!(stage_id: @stage.id, subject_id: @subject.id, shixun_id: shixun.id, position: @stage.stage_shixuns.count + 1)
+ end
+ end
+ @subject.update_attributes(updated_at: Time.now)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("章节创建失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def edit
+
+ end
+
+ def update
+ ActiveRecord::Base.transaction do
+ begin
+ @stage.update_attributes(stage_params)
+ @stage.stage_shixuns.destroy_all
+ unless params[:shixun_id].blank?
+ params[:shixun_id].each do |shixun_id|
+ shixun = Shixun.where(id: shixun_id).first
+ @stage.stage_shixuns.create!(subject_id: @subject.id, shixun_id: shixun.id, position: @stage.stage_shixuns.count + 1) if shixun.present?
+ end
+ end
+ @subject.update_attributes(updated_at: Time.now)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("章节更新失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def destroy
+ ActiveRecord::Base.transaction do
+ @subject.stages.where("position > ?", @stage.position).update_all("position = position - 1")
+ @stage.destroy
+ @status = 1
+ end
+ end
+
+ def up_position
+ ActiveRecord::Base.transaction do
+ begin
+ position = @stage.position
+ tip_exception("第一章不能向上移动") if @stage.position == 1
+ pre_stage = @subject.stages.where(position: position - 1).first
+ pre_stage.update_attributes(position: position)
+ @stage.update_attributes(position: position - 1)
+ rescue Exception => e
+ uid_logger("stage up failed: #{e.message}")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def down_position
+ ActiveRecord::Base.transaction do
+ begin
+ position = @stage.position
+ rails "最后一章不能向下移动" if @stage.position == @subject.stages.count
+ next_stage = @subject.stages.where(position: position + 1).first
+ next_stage.update_attributes(position: position)
+ @stage.update_attributes(position: position + 1)
+ rescue Exception => e
+ uid_logger("stage up failed: #{e.message}")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ private
+ def stage_params
+ tip_exception("章节名称不能为空") if params[:name].blank?
+ params.require(:stage).permit(:name, :description)
+ end
+
+ def find_subject
+ @subject = Subject.find_by!(id: params[:subject_id])
+ end
+
+ def find_stage
+ @stage = Stage.find_by!(id: params[:id])
+ @subject = @stage.subject
+ end
+
+ def allowed
+ unless current_user.manager_of_subject?(@subject)
+ tip_exception("403", "")
+ end
+ end
+end
diff --git a/app/controllers/student_works_controller.rb b/app/controllers/student_works_controller.rb
new file mode 100644
index 000000000..46e87e632
--- /dev/null
+++ b/app/controllers/student_works_controller.rb
@@ -0,0 +1,747 @@
+class StudentWorksController < ApplicationController
+ include HomeworkCommonsHelper
+ include StudentWorksHelper
+
+ before_action :require_login
+ before_action :find_homework, only: [:new, :create, :search_member_list, :check_project, :relate_project,
+ :cancel_relate_project]
+ before_action :find_work, only: [:shixun_work_report, :adjust_review_score, :shixun_work, :commit_des, :update_des,
+ :adjust_score, :show, :adjust_score, :supply_attachments, :revise_attachment,
+ :comment_list, :add_score, :add_score_reply, :destroy_score, :appeal_anonymous_score,
+ :deal_appeal_score, :cancel_appeal, :edit, :update, :export_shixun_work_report]
+ before_action :user_course_identity
+ before_action :allow_add_score, only: [:add_score]
+ before_action :homework_publish
+
+ before_action :teacher_allowed, only: [:adjust_score, :adjust_review_score, :deal_appeal_score]
+ before_action :course_student, only: [:new, :commit_des, :update_des, :create, :edit, :update, :search_member_list, :relate_project,
+ :cancel_relate_project, :relate_project]
+
+ before_action :my_work, only: [:commit_des, :update_des, :edit, :update, :revise_attachment, :appeal_anonymous_score,
+ :cancel_appeal]
+
+ before_action :edit_duration, only: [:edit, :update]
+ before_action :end_or_late, only: [:new, :create, :search_member_list, :commit_des, :update_des]
+
+ before_action :require_score_id, only: [:destroy_score, :add_score_reply, :appeal_anonymous_score, :deal_appeal_score, :cancel_appeal]
+
+ before_action :is_evaluation, only: [:show, :supply_attachments]
+
+ def new
+ uid_logger("#######new current_user : 1111")
+ @current_user = current_user
+ uid_logger("#######new current_user : #{@current_user.id}")
+ if @homework.homework_type == "group" && @homework.homework_detail_group.try(:base_on_project)
+ work = @homework.student_works.find_by(user_id: @current_user.id)
+ if work.present? && (work.work_status != 0 || work.project_id == 0)
+ normal_status(403, "")
+ end
+ end
+ end
+
+ # 搜索课堂学生
+ def search_member_list
+ unless params[:search].blank?
+ @members = @course.students.joins(:user).where("user_id != #{current_user.id} and
+ concat(users.lastname, users.firstname) like ?", "%#{params[:search]}%")
+ else
+
+ # 没有搜索条件时搜索课堂所有未提交的学生
+ user_ids = @homework.student_works.where("work_status = 0").pluck(:user_id) - [current_user.id]
+ @members = @course.students.where(user_id: user_ids)
+ end
+
+ page = params[:page] ? params[:page].to_i : 1
+ limit = params[:limit] ? params[:limit].to_i : 10
+
+ # todo user_extension
+ @members = @members.page(page).per(limit).includes(:course_group, user: :user_extension)
+ end
+
+ def create
+ student_work = @homework.student_works.find_or_create_by(user_id: current_user.id)
+
+ tip_exception("作业不可重复提交") if student_work.work_status != 0
+ update_check student_work
+
+ student_ids = [current_user.id]
+ ActiveRecord::Base.transaction do
+ begin
+ student_work.description = params[:description]
+ student_work.commit_time = Time.now
+ student_work.update_time = Time.now
+ student_work.commit_user_id = current_user.id
+ student_work.group_id = @homework.homework_type == "group" ? @homework.max_group_id : 0
+
+ #提交作品时,计算是否迟交
+ homework_setting = @homework.homework_group_setting(current_user.id)
+ student_work.late_penalty = homework_setting.end_time < Time.now ? @homework.late_penalty : 0
+ student_work.work_status = homework_setting.end_time < Time.now ? 2 : 1
+
+ if student_work.save!
+ Attachment.associate_container(params[:attachment_ids], student_work.id, student_work.class)
+
+ if @homework.homework_type == "group"
+ members = (params[:user_ids] || []).collect(&:to_i) - [current_user.id]
+ members = @course.students.pluck(:user_id) & members
+ student_ids += members
+ for i in 0 .. members.count-1
+ stu_work = @homework.student_works.find_or_initialize_by(user_id: members[i].to_i)
+ stu_work.update_attributes(user_id: members[i].to_i, description: student_work.description,
+ homework_common_id: @homework.id, project_id: student_work.project_id,
+ late_penalty: student_work.late_penalty, work_status: student_work.work_status,
+ commit_time: Time.now, update_time: Time.now, group_id: student_work.group_id,
+ commit_user_id: current_user.id)
+ stu_work.save!
+ student_work.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = attachment.author_id
+ stu_work.attachments << att
+ end
+ end
+ end
+ @homework.update_column(:updated_at, Time.now)
+ # todo 更新对应的作业课堂动态
+ # update_course_activity(@taskhomework.class,@task.id)
+
+ @work_id = student_work.id
+ end
+
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ SubmitStudentWorkNotifyJob.perform_later(@homework.id, student_ids)
+ end
+
+ def edit
+ @current_user = current_user
+ if @homework.homework_type == "group"
+ # todo user_extension
+ @work_members = @course.students.where(user_id: @homework.student_works.where(group_id: @work.group_id).pluck(:user_id)).
+ order("course_members.id=#{@work.user_id} desc").includes(:course_group, user: :user_extension)
+ end
+ end
+
+ def update
+ update_check @work
+
+ student_ids = []
+ ActiveRecord::Base.transaction do
+ begin
+ @work.description = params[:description]
+ @work.update_time = Time.now
+ @work.commit_user_id = current_user.id
+ if @work.save!
+ Attachment.associate_container(params[:attachment_ids], @work.id, @work.class)
+
+ #如果学生作品被打分后修改,应该给老师提示
+ student_ids << @work.user_id if @work.scored?
+
+ if @homework.homework_type == "group"
+ student_works = @homework.student_works.where("group_id = #{@work.group_id} and user_id != #{@work.user_id}")
+ work_user_ids = student_works.pluck(:user_id)
+ params_user_ids = (params[:user_ids] || []).collect(&:to_i) - [@work.user_id]
+ params_user_ids = @course.students.pluck(:user_id) & params_user_ids
+
+ # 原成员更新描述、更新时间以及附件
+ @homework.student_works.where(group_id: @work.group_id, user_id: (work_user_ids & params_user_ids)).each do |work|
+ work.update_attributes(update_time: Time.now, description: @work.description, commit_user_id: current_user.id)
+ work.attachments.destroy_all
+ @work.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = attachment.author_id
+ work.attachments << att
+ end
+ student_ids << work.user_id if work.scored?
+ end
+
+ # 删除的成员
+ delete_user_ids = work_user_ids - params_user_ids
+ @homework.student_works.where(group_id: @work.group_id, user_id: delete_user_ids).each do |work|
+ work.attachments.destroy_all
+ # work.student_works_scores.destroy_all
+ work.tidings.destroy_all
+ end
+ @homework.student_works.where(group_id: @work.group_id, user_id: delete_user_ids).
+ update_all(work_status: 0, description: nil, late_penalty: 0, commit_time: nil, update_time: nil,
+ final_score: nil, teacher_score: nil, student_score: nil, teaching_asistant_score: nil,
+ work_score: nil, project_id: 0, group_id: 0, commit_user_id: nil)
+
+ # 新增加的成员
+ (params_user_ids - work_user_ids).each do |user_id|
+ stu_work = @homework.student_works.find_or_initialize_by(user_id: user_id)
+ stu_work.update_attributes(user_id: user_id, description: @work.description, homework_common_id: @homework.id,
+ project_id: @work.project_id, late_penalty: @work.late_penalty,
+ work_status: @work.work_status, commit_time: Time.now, update_time: Time.now,
+ group_id: @work.group_id, commit_user_id: current_user.id)
+ @work.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = attachment.author_id
+ stu_work.attachments << att
+ end
+
+ student_ids << user_id
+ end
+ end
+
+ normal_status(0,"更新成功")
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+
+ SubmitStudentWorkNotifyJob.perform_later(@homework.id, student_ids) if student_ids.present?
+ end
+ end
+
+ def show
+ @current_user = current_user
+ @work_members = @homework.homework_type != "group" ? [] : @homework.student_works.where.not(user_id: @work.user_id).
+ where(group_id: @work.group_id).includes(:user)
+ @attachments = @work.attachments.where.not(attachtype: 7)
+ end
+
+ # 判断项目是否已有其他作品关联上了
+ def check_project
+ tip_exception("项目id不能为空") if params[:project_id].blank?
+ work = @homework.student_works.find_by_project_id(params[:project_id])
+ @is_relate = work.present?
+ @relate_user = work.present? ? work.user.real_name : ""
+ end
+
+ def relate_project
+ tip_exception("项目id不能为空") if params[:project_id].blank?
+
+ ActiveRecord::Base.transaction do
+ begin
+ # 判断项目是否存在且当前用户是项目管理员
+ project = Project.find_by_id(params[:project_id])
+ member = Member.find_by_project_id_and_user_id(project.try(:id), current_user.id)
+
+ if project.present? && member.present? && member.member_roles.take.try(:role_id) == 3
+
+ work = @homework.student_works.find_or_create_by(user_id: current_user.id)
+
+ if work.work_status == 0 && work.project_id == 0
+ work.update_attributes(project_id: project.id, update_time: Time.now)
+
+ # 将老师加入项目
+ project_member = project.members.find_by_user_id(@homework.user_id)
+ if project_member.present?
+ project_member.member_roles.take.update_attributes(role_id: 3) if project_member.member_roles.take.present?
+ else
+ member = Member.create(user_id: @homework.user_id, project_id: project.id)
+ member.member_roles << MemberRole.new(role_id: 3)
+ Tiding.create(user_id: @homework.user_id, trigger_user_id: current_user.id, container_id: project.id,
+ container_type: 'ManagerJoinProject', belong_container_id: project.id,
+ belong_container_type: "Project", tiding_type: "System", extra: 3)
+ end
+ normal_status(0,"关联成功")
+ else
+ tip_exception("不能重复关联项目")
+ end
+ else
+ tip_exception("该项目不存在")
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def cancel_relate_project
+ work = @homework.student_works.find_by_user_id_and_work_status(current_user.id, 0)
+ if work.present? && work.project.present?
+ ActiveRecord::Base.transaction do
+ begin
+ member = work.project.members.find_by_user_id(@homework.user_id)
+ member.destroy if member.present?
+ Tiding.where(user_id: @homework.user_id, trigger_user_id: current_user.id, container_id: work.project.id,
+ container_type: 'ManagerJoinProject').destroy_all
+
+ work.update_attributes(project_id: 0)
+ normal_status(0,"取消关联成功")
+
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ else
+ tip_exception("无法取消关联")
+ end
+ end
+
+ def supply_attachments
+ @revise_attachments = @work.attachments.where(attachtype: 7)
+ @last_atta = @revise_attachments.last
+ end
+
+ def revise_attachment
+ tip_exception("不在补交阶段内") unless @homework.late_duration
+ tip_exception("附件参数有误") if params[:attachment_ids].blank? || !params[:attachment_ids].is_a?(Array)
+ tip_exception("补交附件原因不能为空") if params[:description].blank?
+
+ ActiveRecord::Base.transaction do
+ begin
+ revise_attachment = @work.attachments.where(attachtype: 7).reorder("created_on desc").last
+ if revise_attachment.present? && @work.student_works_scores.where("created_at > '#{revise_attachment.created_on}'
+ and score is not null").count == 0
+ revise_attachment.destroy
+ end
+ Attachment.associate_container(params[:attachment_ids], @work.id, @work.class, 7)
+ revise_attachment = Attachment.where(attachtype: 7, container_id: @work.id, container_type: "StudentWork").last
+ revise_attachment.update_attributes(description: params[:description]) if revise_attachment.present?
+
+ @work.update_attributes(update_time: Time.now)
+
+ normal_status(0, "提交成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def comment_list
+ @current_user = current_user
+ @last_comment = @work.student_works_scores.where(user_id: @current_user.id).last
+ # todo user_extension
+ @comment_scores = (@user_course_identity < Course::STUDENT || current_user == @work.user) ?
+ @work.student_works_scores.reorder("created_at desc") :
+ @work.student_works_scores.where(user_id: current_user.id).reorder("created_at desc")
+ @comment_scores = @comment_scores.includes(:student_works_scores_appeal, :attachments, journals_for_messages: :user, user: :user_extension)
+ end
+
+ # 给作品评分
+ def add_score
+ tip_exception("该学生的分数已经过调整,不能再评阅") if @work.ultimate_score
+ tip_exception("分数和评语不能都为空") if params[:score].blank? && params[:comment].blank?
+ tip_exception("分数不能超过0-100") if params[:score] && (params[:score].to_f < 0 || params[:score].to_f > 100)
+
+ ActiveRecord::Base.transaction do
+ begin
+ # 没传score则取上次评分成绩
+ score = StudentWorksScore.where(user_id: current_user.id, student_work_id: @work.id).last
+ new_score = StudentWorksScore.new
+ new_score.score = params[:score].blank? ? score.try(:score) : params[:score].to_f
+ new_score.comment = params[:comment] if params[:comment] && params[:comment].strip != ""
+ new_score.user_id = current_user.id
+ new_score.student_work_id = @work.id
+
+ # 如果作品是未提交的状态则更新为已提交
+ if @user_course_identity < Course::STUDENT && !new_score.score.nil? && @work.work_status == 0
+ @work.update_attributes(work_status: 1, commit_time: Time.now)
+ # 分组作业更新分组id
+ @work.update_attributes(group_id: @homework.max_group_id) if @homework.homework_type == "group"
+ end
+
+ new_score.reviewer_role = @user_course_identity == Course::STUDENT ? 3 : @user_course_identity == Course::ASSISTANT_PROFESSOR ? 2 : 1
+
+ if new_score.save!
+ Attachment.associate_container(params[:attachment_ids], new_score.id, new_score.class)
+
+ # 该用户的历史评阅无效
+ score.update_column('is_invalid', true) if score.present? && score.score.present?
+
+ Tiding.create(user_id: @work.user_id, trigger_user_id: User.current.id, container_id: new_score.id,
+ container_type: "StudentWorksScore", parent_container_id: @work.id,
+ parent_container_type: "HomeworkCommon", belong_container_id: @homework.course_id,
+ belong_container_type: "Course", viewed: 0, tiding_type: "HomeworkCommon", extra: new_score.reviewer_role)
+
+ case new_score.reviewer_role
+ when 1 #教师评分:最后一个教师评分为最终评分
+ @work.teacher_score = new_score.score
+ if @homework.homework_type == "group" && params[:same_score]
+ add_score_to_member @work, @homework, new_score
+ end
+ when 2 #教辅评分 教辅评分显示平均分
+ # 助教评分:普通模式则是平均分,复审模式则是最新评分
+ if @homework.homework_detail_manual.ta_mode == 1
+ @work.teaching_asistant_score = new_score.ta_score @work.id
+ else
+ @work.teaching_asistant_score = new_score.score
+ end
+
+ if @homework.homework_type == "group" && params[:same_score]
+ add_score_to_member @work, @homework, new_score
+ end
+ when 3 #学生评分 学生评分显示平均分
+ # 匿评分
+ @work.student_score = new_score.stu_score(@work.id)
+ if @homework.homework_type == "group" && params[:same_score] && new_score.score.present?
+ add_score_to_member @work, @homework, new_score
+ end
+
+ current_user.student_works_scores.where(student_work_id: @work.id, reviewer_role: 3, appeal_status: 1).update_all(appeal_status: 5)
+ end
+
+ @homework.update_column('updated_at', Time.now)
+ # update_course_activity(@homework.class,@homework.id)
+ @work.save!
+
+ normal_status(0,"提交成功")
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 实训作品的提交总结
+ def commit_des
+ @current_user = current_user
+ end
+
+ # 实训作品的总结
+ def update_des
+ @work.update_attributes(des_params)
+ tip_exception(0, "提交成功")
+ end
+
+ # 实训作品弹框
+ def shixun_work
+ @myshixun = @work.myshixun
+ if @myshixun.present?
+ @current_user = current_user
+ @work_user = @work.user
+ @shixun = @homework.shixuns.take
+ else
+ tip_exception("作品还未提交")
+ end
+ end
+
+ # 实训报告
+ def shixun_work_report
+ @user = @work.user
+ @shixun = @homework.shixuns.take
+ @games = @work.myshixun.games.includes(:challenge, :game_codes,:outputs) if @work.myshixun
+
+ # 用户最大评测次数
+ @user_evaluate_count = @games.inject(0){|sum, g| sum + g.outputs.pluck(:query_index).first.to_i } if @games
+ # 图形效率图的数据
+ @echart_data = student_efficiency(@homework, @work)
+ end
+
+ def export_shixun_work_report
+ @user = @work.user
+ @shixun = @homework.shixuns.take
+ @games = @work.myshixun.games.includes(:challenge, :game_codes,:outputs) if @work.myshixun
+
+ # 用户最大评测次数
+ @user_evaluate_count = @games.inject(0){|sum, g| sum + g.outputs.pluck(:query_index).first.to_i } if @games
+ # 图形效率图的数据
+ @echart_data = student_efficiency(@homework, @work)
+ @myself_eff = @echart_data[:efficiency_list].find { |item| item.last == @user.id }
+ @myself_consume = @echart_data[:consume_list].find { |item| item.last == @user.id }
+ # filename = "实训报告_#{@shixun.name}_#{@use.real_name}_#{Time.current.strftime('%Y%m%d%H%M%S')}.pdf" #下载报错 unknown nil name,下面为修改的-hs-0606
+
+ filename = "实训报告_#{@shixun&.name}_#{@use&.real_name}_#{Time.current.strftime('%Y%m%d%H%M%S')}.pdf"
+ stylesheets = %w(shixun_work/shixun_work.css shared/codemirror.css)
+ render pdf: 'shixun_work/shixun_work', filename: filename, stylesheets: stylesheets
+ end
+
+ # 作品调分
+ def adjust_score
+ tip_exception("分数不能为空") if params[:score].blank?
+ tip_exception("分数不能超过0-100") if params[:score].to_f < 0 || params[:score].to_f > 100
+ ActiveRecord::Base.transaction do
+ begin
+ # 分数不为空的历史评阅都置为失效
+ @work.student_works_scores.where.not(score: nil).update_all(is_invalid: 1)
+ reviewer_role = @user_course_identity == Course::ASSISTANT_PROFESSOR ? 2 : 1
+ new_score = StudentWorksScore.new(student_work_id: @work.id, score: params[:score].to_f, comment: "使用调分功能调整了作业最终成绩:#{params[:comment]}",
+ user_id: current_user.id, reviewer_role: reviewer_role, is_ultimate: 1)
+ new_score.save!
+
+ # 如果作品是未提交的状态则更新为已提交
+ if @work.work_status == 0
+ @work.work_status = 1
+ @work.commit_time = Time.now
+ # 分组作业更新分组id
+ @work.group_id = @homework.max_group_id if @homework.homework_type == "group"
+ end
+
+ @work.ultimate_score = true
+ @work.work_score = params[:score].to_f
+ @work.save!
+
+ normal_status(0,"调分成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ #添加评分的回复
+ def add_score_reply
+ tip_exception("回复内容不能为空") if params[:comment].blank?
+ ActiveRecord::Base.transaction do
+ begin
+ score = @work.student_works_scores.find_by!(id: params[:score_id])
+ jour = score.journals_for_messages.new(user_id: current_user.id, notes: params[:comment], reply_id: score.user_id)
+ jour.save
+ normal_status(0,"回复成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 删除教师/教辅的评分记录
+ def destroy_score
+ score = @work.student_works_scores.find_by(id: params[:score_id])
+ tip_exception("该评阅记录不存在") unless score.present?
+ tip_exception("该评阅记录不能删除") unless score.allow_delete(@current_user, @user_course_identity)
+ begin
+ score.destroy
+ normal_status(0,"删除成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ # 对学生匿评进行申诉
+ def appeal_anonymous_score
+ tip_exception("申诉原因不能为空") if params[:comment].blank?
+ score = @work.student_works_scores.find_by(id: params[:score_id].to_i)
+ tip_exception("无法申诉") unless score.present? && @homework.appeal_duration &&
+ score.reviewer_role == 3 && score.appeal_status == 0
+ score_appeal = nil
+ ActiveRecord::Base.transaction do
+ begin
+ score.update_attributes(appeal_status: 1)
+ score_appeal = StudentWorksScoresAppeal.create!(user_id: current_user.id, student_works_score_id: score.id,
+ comment: params[:comment], appeal_status: 1)
+
+ normal_status(0,"提交成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ # 提交后给老师和助教、匿评人发消息
+ StudentWorkScoreAppealNotifyJob.perform_later(@course.id, score_appeal.id, current_user.id)
+ end
+
+ # 撤销申诉
+ def cancel_appeal
+ score = @work.student_works_scores.find_by(id: params[:score_id].to_i)
+ if score.present? && score.appeal_status == 1
+ ActiveRecord::Base.transaction do
+ begin
+ score.update_attributes(appeal_status: 2)
+ score_appeal = score.student_works_scores_appeal
+ score_appeal.update_attributes(appeal_status: 2)
+ score_appeal.tidings.destroy_all
+ normal_status(0,"撤销成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ else
+ tip_exception("无法撤销")
+ end
+ end
+
+ # status 3 接受 4 拒绝
+ def deal_appeal_score
+ tip_exception("缺少status参数") if params[:status].blank?
+ tip_exception("status值不合要求") unless params[:status].to_i == 3 || params[:status].to_i == 4
+ score = @work.student_works_scores.find_by(id: params[:score_id].to_i)
+ if score.present? && score.appeal_status == 1
+ ActiveRecord::Base.transaction do
+ begin
+ # 更新appeal_status的值
+ score.update_attributes(appeal_status: params[:status].to_i)
+ score_appeal = score.student_works_scores_appeal
+ score_appeal.update_attributes(appeal_status: params[:status].to_i)
+ score_appeal.tidings.update_all(status: 1)
+
+ if params[:status].to_i == 3
+ # 申诉成功后 扣匿评学生的违规匿评扣分
+ sw = @homework.student_works.find_by(user_id: score.user_id)
+ sw.update_attribute("appeal_penalty", @homework.homework_detail_manual.appeal_penalty + sw.appeal_penalty) if sw.present?
+
+ # 申诉成功 重新计算申诉者的匿评分
+ if @work.student_works_scores.where("reviewer_role = 3 AND appeal_status != 3").count > 0
+ @work.student_score = score.stu_score(@work.id)
+ else
+ @work.student_score = nil
+ end
+ @work.save
+ end
+
+ # todo tiding
+ Tiding.create(user_id: score_appeal.user_id, trigger_user_id: current_user.id, container_id: score_appeal.id,
+ container_type: "StudentWorksScoresAppeal", parent_container_id: @work.id,
+ parent_container_type: 'UserAppealResult', belong_container_id: @course.id,
+ belong_container_type: "Course", viewed: 0, status: params[:status].to_i == 3 ? 1 : 2,
+ tiding_type: "HomeworkCommon")
+ Tiding.create(user_id: score.user_id, trigger_user_id: current_user.id, container_id: score_appeal.id,
+ container_type: "StudentWorksScoresAppeal", parent_container_id: @work.id,
+ parent_container_type: 'AppealResult', belong_container_id: @course.id, belong_container_type: "Course",
+ viewed: 0, status: params[:status].to_i == 3 ? 1 : 2, tiding_type: "HomeworkCommon")
+
+ normal_status(0,"提交成功")
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ else
+ tip_exception("该申诉不存在")
+ end
+ end
+
+ # 查重作品调分
+ def adjust_review_score
+ if params[:score].nil? || params[:challenge_id].nil? || params[:code_rate].nil? || params[:copy_user_id].nil?
+ tip_exception("参数错误,score和challenge_id和code_rate和copy_user_id不能为空")
+ end
+ copy_user = User.find params[:copy_user_id]
+ comment = "代码查重结果显示与#{copy_user.try(:show_real_name)}的代码相似度#{params[:code_rate]}%"
+ @work.challenge_work_scores.create(challenge_id: params[:challenge_id], user_id: current_user.id, score: params[:score],
+ comment: comment)
+ HomeworksService.new.set_shixun_final_score(@work)
+ @work_score = @homework.student_works.find_by(id: @work.id).try(:work_score)
+
+ end
+
+
+ private
+
+ def find_homework
+ begin
+ @homework = HomeworkCommon.find params[:homework_common_id]
+ @course = @homework.course
+ rescue Exception => e
+ uid_logger_error("##########{e.message}")
+ missing_template
+ end
+ end
+
+ def find_work
+ begin
+ @work = StudentWork.find params[:id]
+ @homework = @work.homework_common
+ @course = @homework.course
+ rescue Exception => e
+ uid_logger_error("##########{e.message}")
+ missing_template
+ end
+ end
+
+ def homework_public
+ tip_exception(403,"没有操作权限") unless @user_course_identity <= Course::STUDENT ||
+ (@course.is_public == 1 && @homework.is_public)
+ end
+
+ def course_student
+ uid_logger("#########course-student")
+ tip_exception(403,"没有操作权限") if @user_course_identity != Course::STUDENT
+ end
+
+ def my_work
+ tip_exception(403,"没有操作权限") if @work.user_id != current_user.id || @work.work_status == 0
+ end
+
+ def edit_duration
+ tip_exception("已过了修改时间") if @homework.end_time && @homework.end_time < Time.now
+ end
+
+ def end_or_late
+ tip_exception("不在提交/更新阶段") if @homework.end_or_late
+ end
+
+ def des_params
+ tip_exception("description参数不能为空") if params[:description].blank?
+ params.require(:student_work).permit(:description)
+ end
+
+ def require_score_id
+ tip_exception("score_id参数不能为空") if params[:score_id].blank?
+ end
+
+ # 是否匿评阶段
+ def is_evaluation
+ @is_author = @work.user_id == current_user.id
+ @is_evaluation = @user_course_identity == Course::STUDENT && !@is_author && @homework.anonymous_comment &&
+ [3, 4].include?(@homework.homework_detail_manual.comment_status)
+ end
+
+ def allow_add_score
+ # 老师始终有评阅权限,匿评阶段内,学生对分配给该学生的作品有评阅权限
+ tip_exception(403, "没有权限") unless allow_score(@homework, @user_course_identity, current_user.id, @work)
+ end
+
+ def update_check work
+ tip_exception("作品描述不能为空") if params[:description].blank?
+ if @homework.homework_type == "group"
+ tip_exception("小组成员不能为空") if params[:user_ids].blank?
+ tip_exception("小组成员人数不合要求") if params[:user_ids].length > @homework.homework_detail_group.max_num ||
+ params[:user_ids].length < @homework.homework_detail_group.min_num
+ tip_exception("请先关联项目") if @homework.homework_detail_group.base_on_project && work.project_id == 0
+ end
+ end
+
+
+ def add_score_to_member student_work, homework, new_score
+ student_works = homework.student_works.where("group_id = #{student_work.group_id} and id != #{student_work.id} and ultimate_score = 0")
+ student_works.each do |st_work|
+ st_score = StudentWorksScore.new(user_id: new_score.user_id, score: new_score.score,
+ reviewer_role: new_score.reviewer_role, comment: new_score.comment)
+ st_work.student_works_scores << st_score
+
+ score = StudentWorksScore.where(user_id: new_score.user_id, student_work_id: st_work.id).last
+ # 该用户的历史评阅无效
+ score.update_column('is_invalid', true) if score.present? && score.score.present?
+
+ if new_score.reviewer_role == 1
+ st_work.teacher_score = new_score.score
+ elsif new_score.reviewer_role == 2
+ if homework.homework_detail_manual.ta_mode == 1
+ st_work.teaching_asistant_score = new_score.ta_score st_work.id
+ else
+ st_work.teaching_asistant_score = new_score.score
+ end
+ else
+ st_work.student_score = student_work.student_score
+ end
+ st_work.save
+
+ Tiding.create(user_id: st_work.user_id, trigger_user_id: current_user.id, container_id: st_score.id,
+ container_type: "StudentWorksScore", parent_container_id: st_work.id, parent_container_type: "StudentWork",
+ belong_container_id: homework.course_id, belong_container_type: "Course", viewed: 0,
+ tiding_type: "HomeworkCommon", extra: new_score.reviewer_role)
+
+ new_score.attachments.each do |attachment|
+ att = attachment.copy
+ att.author_id = st_score.user_id
+ st_score.attachments << att
+ end
+ end
+ end
+end
diff --git a/app/controllers/subjects_controller.rb b/app/controllers/subjects_controller.rb
new file mode 100644
index 000000000..4b954584b
--- /dev/null
+++ b/app/controllers/subjects_controller.rb
@@ -0,0 +1,413 @@
+class SubjectsController < ApplicationController
+ before_action :require_login, except: [:index]
+ # before_action :check_auth, except: [:index]
+ before_action :find_subject, except: [:index, :create, :append_to_stage]
+ before_action :allowed, only: [:update, :edit, :destroy, :publish, :cancel_publish, :cancel_has_publish,
+ :search_members, :add_subject_members, :statistics, :shixun_report, :school_report,
+ :up_member_position, :down_member_position]
+
+ include ApplicationHelper
+
+ def index
+ @tech_system = Repertoire.where(nil).order("updated_at desc")
+ select = params[:select] # 路径导航类型
+ reorder = params[:order] || "publish_time"
+ search = params[:search]
+
+ ## 分页参数
+ page = params[:page] || 1
+ limit = params[:limit] || 16
+ offset = (page.to_i-1) * limit
+
+ # 最热排序
+ if reorder == "myshixun_count"
+ if select
+ @subjects = Subject.find_by_sql("SELECT subjects.id, subjects.name, subjects.stages_count, subjects.repertoire_id, subjects.status,
+ subjects.shixuns_count, COUNT(myshixuns.id) AS myshixun_member_count FROM myshixuns, stage_shixuns, subjects
+ WHERE myshixuns.shixun_id = stage_shixuns.shixun_id AND stage_shixuns.subject_id = subjects.id
+ AND `subjects`.`hidden` = 0 AND `subjects`.`status` = 2 AND `subjects`.`name` like '%#{search}%'
+ AND `subjects`.`repertoire_id` = #{select} GROUP BY subjects.id ORDER BY myshixun_member_count DESC")
+ else
+ @subjects = Subject.find_by_sql("SELECT subjects.id, subjects.name, subjects.stages_count, subjects.repertoire_id, subjects.status,
+ subjects.shixuns_count, COUNT(myshixuns.id) AS myshixun_member_count FROM myshixuns, stage_shixuns, subjects
+ WHERE myshixuns.shixun_id = stage_shixuns.shixun_id AND stage_shixuns.subject_id = subjects.id
+ AND `subjects`.`hidden` = 0 AND `subjects`.`status` = 2 AND `subjects`.`name` like '%#{search}%'
+ GROUP BY subjects.id ORDER BY myshixun_member_count DESC")
+ end
+ else
+ # 我的路径
+ if reorder == "mine"
+ mine_subject_id = StageShixun.find_by_sql("select DISTINCT(subject_id) from stage_shixuns where shixun_id in
+ (select distinct(shixun_id) from myshixuns where user_id=#{current_user.id})").map(&:subject_id)
+ manage_subject_id = SubjectMember.where(user_id: current_user.id).pluck(:subject_id)
+ total_subject_id = (mine_subject_id + manage_subject_id).uniq
+ @subjects = Subject.where(id: total_subject_id)
+ elsif reorder == "publish_time"
+ @subjects = Subject.unhidden
+ else
+ @subjects = Subject.visible.unhidden
+ end
+
+ # 类型
+ if select
+ @subjects = @subjects.where(repertoire_id: select)
+ end
+
+ if search.present?
+ @subjects = @subjects.where("name like ?", "%#{search}%")
+ end
+
+ # 排序
+ order_str = reorder == "publish_time" ? "status = 2 desc, publish_time asc" : "updated_at desc"
+ @subjects = @subjects.reorder(order_str)
+ end
+
+ @total_count = @subjects.size
+
+ if reorder != "myshixun_count"
+ @subjects = @subjects.page(page).per(limit).includes(:shixuns)
+ else
+ @subjects = @subjects[offset, limit]
+ end
+ end
+
+ def show
+ @user = current_user
+ @is_creator = current_user.creator_of_subject?(@subject)
+ # 合作团队
+ @members = @subject.subject_members.includes(:user)
+ challenge_ids = Challenge.where(shixun_id: @subject.shixuns.published.pluck(:id)).pluck(:id)
+ # 实训路径中的所有实训标签
+ @tags = ChallengeTag.where(challenge_id: challenge_ids).pluck(:name).uniq
+ # 用户获取的实训标签
+ @user_tags = @subject.shixuns.map(&:user_tags_name).flatten.uniq
+ end
+
+ def create
+ ActiveRecord::Base.transaction do
+ begin
+ @subject = Subject.new(subject_params)
+ @subject.user_id = current_user.id
+ @subject.save!
+ @subject.subject_members.create!(role: 1, user_id: current_user.id)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("实训路径创建失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def edit
+ end
+
+ def update
+ begin
+ @subject.update_attributes(subject_params)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("实训路径更新失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def destroy
+ ActiveRecord::Base.transaction do
+ begin
+ ApplyAction.where(container_type: "ApplySubject", container_id: @subject.id).destroy_all
+ @subject.destroy
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("实训路径删除失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def choose_subject_shixun
+ @search = params[:search]
+ @type = params[:type]
+ # 超级管理员用户显示所有未隐藏的实训、非管理员显示合作团队用户的实训(对本单位公开且未隐藏)
+ if current_user.admin?
+ @shixuns = Shixun.select([:id, :name, :status, :myshixuns_count, :identifier]).where(hidden: 0)
+ else
+ none_shixun_ids = ShixunSchool.where("school_id != #{current_user.user_extension.try(:school_id)}").pluck(:shixun_id)
+
+ @shixuns = Shixun.select([:id, :name, :status, :myshixuns_count, :identifier]).where.not(id: none_shixun_ids).where(hidden: 0)
+ end
+
+ # 实训课程的所有标签
+ tag_ids = @shixuns.joins(:shixun_tag_repertoires).pluck(:tag_repertoire_id).uniq
+ @tags = TagRepertoire.select([:id, :name]).where(id: tag_ids)
+
+ if params[:search]
+ @shixuns = @shixuns.where("name like ?", "%#{@search}%")
+ end
+
+ unless @type.nil? || @type == "" || @type == "all"
+ shixun_ids = ShixunTagRepertoire.where(tag_repertoire_id: @type).pluck(:shixun_id).uniq
+ @shixuns = @shixuns.where(id: shixun_ids)
+ end
+
+ @shixuns = @shixuns.reorder("created_at desc")
+ @shixuns_count = @shixuns.size
+
+ ## 分页参数
+ page = params[:page] || 1
+ @shixuns = @shixuns.page(page).per(10)
+
+ @shixuns = @shixuns.includes(:myshixuns)
+ end
+
+ def append_to_stage
+ @shixuns = Shixun.where(id: params[:shixun_id]).order("id desc")
+ end
+
+ def choose_course
+ course_ids = Course.find_by_sql("SELECT c.id FROM courses c, members m, member_roles mr
+ WHERE m.course_id = c.id AND m.id=mr.member_id AND mr.role_id in (3,7,9)
+ AND m.user_id=#{current_user.id} AND c.is_delete = 0 AND c.is_end = 0").map(&:id)
+ @courses = Course.where(id: course_ids)
+ @none_shixun_ids = ShixunSchool.where("school_id != #{current_user.user_extension.try(:school_id)}").pluck(:shixun_id)
+ end
+
+ def send_to_course
+ @course = Course.find_by!(id: params[:course_id])
+ stages = @subject.stages.where(id: @subject.stage_shixuns.where(shixun_id: params[:shixun_ids]).pluck(:stage_id))
+
+ course_module = @course.course_modules.where(module_type: "shixun_homework").first
+
+ ActiveRecord::Base.transaction do
+ begin
+ # 将实训课程下的所有已发布实训按顺序发送到课堂,同时创建与章节同名的实训作业目录
+ stages.each do |stage|
+ category = CourseSecondCategory.where(name: stage.name, course_id: @course.id, category_type: "shixun_homework").first ||
+ CourseSecondCategory.create!(name: stage.name, course_id: @course.id, category_type: "shixun_homework",
+ course_module_id: course_module, position: course_module.course_second_categories.count + 1)
+
+ stage.shixuns.where(id: params[:shixun_ids], status: 2).each do |shixun|
+ homework = HomeworksService.new.create_homework shixun, @course, category, current_user
+ end
+ end
+ rescue Exception => e
+ uid_logger(e.message)
+ tip_exception("发送失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def publish
+ apply = ApplyAction.where(container_type: "ApplySubject", container_id: @subject.id).order("created_at desc").first
+ if apply && apply.status == 0
+ @status = 0
+ else
+ @subject.update_attributes(status: 1)
+ ApplyAction.create(container_type: "ApplySubject", container_id: @subject.id, user_id: current_user.id, status: 0)
+ begin
+ status = Trustie::Sms.send(mobile: '18711011226', send_type:'publish_subject' , name: '管理员')
+ rescue => e
+ uid_logger_error("发送验证码出错: #{e}")
+ end
+ @status = 1
+ end
+ end
+
+ def cancel_publish
+ begin
+ apply = ApplyAction.where(container_type: "ApplySubject", container_id: @subject.id).order("created_at desc").first
+ if apply && apply.status == 0
+ apply.update_attributes(status: 3)
+ apply.tidings.destroy_all
+ end
+ @subject.update_attributes(status: 0)
+ rescue => e
+ uid_logger_error(e.message)
+ tip_exception("撤销申请失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def cancel_has_publish
+ begin
+ @subject.update_attributes(:status => 0)
+ rescue => e
+ uid_logger_error(e.message)
+ tip_exception("撤销发布失败")
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def search_members
+ tip_exception("搜索内容不能为空") unless params[:search]
+ page = params[:page] || 1
+ member_ids = @subject.subject_members.map(&:user_id).join(',')
+ condition = "%#{params[:search].strip}%".gsub(" ","")
+ @users = User.where("id not in (?) and status = 1 and LOWER(concat(lastname, firstname, login, mail)) LIKE ?", member_ids, "#{condition}")
+
+ @users = @users.page(page).per(10)
+ @users = @users.includes(:user_extension)
+ end
+
+ def add_subject_members
+ tip_exception(403, "没权限操作") if !current_user.admin?
+ tip_exception("user_ids 不能为空!") if params[:user_ids].blank?
+ memberships = params[:user_ids]
+ memberships.each do |member|
+ if SubjectMember.where(user_id: member, subject_id: @subject.id).count == 0
+ user = User.find_by!(id: member)
+ SubjectMember.create!(user_id: member, subject_id: @subject.id, role: 2, position: @subject.subject_members.size + 1) if user.present?
+ end
+ end
+ end
+
+ # 删除实训
+ # DELETE: /api/subejcts/:id/delete_member
+ def delete_member
+ tip_exception(403, "没权限操作") if !current_user.admin?
+ tip_exception('用户id不能为空') if params[:user_id].blank?
+ user = @subject.subject_members.where(:user_id => params[:user_id], :role => 2).first
+ tip_exception("管理员用户不允许删除,或用户不存在") if user.blank?
+ ActiveRecord::Base.transaction do
+ begin
+ @subject.subject_members.where("position > #{user.position}").update_all("position = position - 1")
+ user.destroy
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 合作者上移
+ def up_member_position
+ tip_exception('用户id不能为空') if params[:user_id].blank?
+ ActiveRecord::Base.transaction do
+ begin
+ member = @subject.subject_members.where(user_id: params[:user_id]).first
+ # position为1时不能再往上移
+ tip_exception('不能再上移了') if member.position == 1
+
+ up_member = @subject.subject_members.where(position: member.position - 1).first
+ up_member.update_attribute(:position, member.position)
+ member.update_attribute(:position, member.position - 1)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ # 合作者下移
+ def down_member_position
+ tip_exception('用户id不能为空') if params[:user_id].blank?
+ ActiveRecord::Base.transaction do
+ begin
+ member = @subject.subject_members.where(user_id: params[:user_id]).first
+
+ # position已经是最大值时不能再往下移
+ tip_exception('不能再下移了') if member.position == @subject.subject_members.size
+
+ down_member = @subject.subject_members.where(:position => member.position + 1).first
+ down_member.update_attribute(:position, member.position)
+ member.update_attribute(:position, member.position + 1)
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+
+ def statistics
+ @learn_count = @subject.member_count
+ shixun_ids = @subject.stage_shixuns.pluck(:shixun_id)
+ # 受用课堂(已经发布的实训(在此路径中的实训)作业的个数)
+ homework_common_id = HomeworkCommonsShixun.where(shixun_id: shixun_ids).pluck(:homework_common_id).uniq
+ homework_common_id = homework_common_id.blank? ? -1 : homework_common_id.join(",")
+
+ courses = Course.find_by_sql("select c.id, c.school_id from courses c right join homework_commons hc on c.id = hc.course_id where c.is_delete = 0
+ and c.school_id is not null and hc.publish_time < '#{Time.now}' and hc.id in (#{(homework_common_id)})")
+ course_ids = courses.pluck(:id).uniq
+ @course_count = course_ids.length
+ # 受用院校
+ school_ids = courses.pluck(:school_id).uniq
+ @schools_count = school_ids.length
+
+ # 采用课堂情况
+ @schools = School.select([:id, :name]).where(id: school_ids)
+ @schools =
+ @schools.map do |s|
+ school_courses = Course.where(id: course_ids, school_id: s.id)
+ course_count = school_courses.count
+ student_count = StudentsForCourse.where(course_id: school_courses.pluck(:id)).count
+ homework_count = HomeworkCommon.find_by_sql("select count(*) cnt from homework_commons hc join courses c on hc.course_id = c.id
+ where c.school_id = #{s.id} and hc.id in(#{homework_common_id})").first.try(:cnt)
+ s.attributes.dup.merge({name: s.name, course_count: course_count, student_count: student_count,homework_count: homework_count})
+ end
+ @schools = @schools.sort{|x,y| y['homework_count'] <=> x['homework_count']}
+ @school_total_count = @schools.size
+
+ page = params[:page] || 1
+ @schools = @schools[(page.to_i-1)*10, 10]
+
+ # TODO: 这个可以异步加载,让页面刷新完成后再加载图形数据
+ # 章节使用情况
+ @stage_user_info = []
+ @sum = 0 #总数
+ @subject.stages.includes(:stage_shixuns).each do |stage|
+ shixun_ids = stage.stage_shixuns.pluck(:shixun_id)
+ if shixun_ids.present?
+ homework_common_id = HomeworkCommonsShixun.where(shixun_id: shixun_ids).pluck(:homework_common_id).uniq
+ if homework_common_id.present?
+ publish_homework = HomeworkDetailManual.where("homework_common_id in(?) and comment_status > 0", homework_common_id.join(",")).pluck(:homework_common_id)
+ use_count = publish_homework.present? ? HomeworkCommon.find_by_sql("select count(*) cnt from homework_commons hc join courses c on hc.course_id = c.id
+ where hc.id in(#{publish_homework.join(",")}) and c.school_id is not null").first.try(:cnt) : 0
+ @sum += use_count
+ else
+ @sum += 0
+ use_count = 0
+ end
+ @stage_user_info << use_count
+ else
+ @sum += 0
+ @stage_user_info << 0
+ end
+ end
+ end
+
+ def shixun_report
+
+ end
+
+ def school_report
+ @schools = School.find_by_sql("select count(ms.id) ue_count, s.id, s.name school_name from user_extensions ue,
+ myshixuns ms, schools s where ue.user_id = ms.user_id and ms.shixun_id in (select shixun_id from
+ stage_shixuns where subject_id = '#{@subject.id}') and s.id = ue.school_id group by ue.school_id
+ order by ue_count desc limit 10")
+ end
+
+ private
+ def subject_params
+ tip_exception("实训路径名称不能为空") if params[:name].blank?
+ tip_exception("实训路径简介不能为空") if params[:description].blank?
+ tip_exception("实训路径学习须知不能为空") if params[:learning_notes].blank?
+ params.require(:subject).permit(:name, :description, :learning_notes)
+ end
+
+ def find_subject
+ @subject = Subject.find_by!(id: params[:id])
+
+ unless @subject.status == 2 || current_user.manager_of_subject?(@subject)
+ tip_exception("403", "")
+ end
+ end
+
+ def allowed
+ unless current_user.manager_of_subject?(@subject)
+ tip_exception("403", "")
+ end
+ end
+end
diff --git a/app/controllers/tem_tests_controller.rb b/app/controllers/tem_tests_controller.rb
new file mode 100644
index 000000000..a3336f3b2
--- /dev/null
+++ b/app/controllers/tem_tests_controller.rb
@@ -0,0 +1,74 @@
+class TemTestsController < ApplicationController
+ before_action :set_tem_test, only: [:show, :edit, :update, :destroy]
+
+ # GET /tem_tests
+ # GET /tem_tests.json
+ def index
+ @tem_tests = TemTest.all
+ end
+
+ # GET /tem_tests/1
+ # GET /tem_tests/1.json
+ def show
+ end
+
+ # GET /tem_tests/new
+ def new
+ @tem_test = TemTest.new
+ end
+
+ # GET /tem_tests/1/edit
+ def edit
+ end
+
+ # POST /tem_tests
+ # POST /tem_tests.json
+ def create
+ @tem_test = TemTest.new(tem_test_params)
+
+ respond_to do |format|
+ if @tem_test.save
+ format.html { redirect_to @tem_test, notice: 'Tem test was successfully created.' }
+ format.json { render :show, status: :created, location: @tem_test }
+ else
+ format.html { render :new }
+ format.json { render json: @tem_test.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /tem_tests/1
+ # PATCH/PUT /tem_tests/1.json
+ def update
+ respond_to do |format|
+ if @tem_test.update(tem_test_params)
+ format.html { redirect_to @tem_test, notice: 'Tem test was successfully updated.' }
+ format.json { render :show, status: :ok, location: @tem_test }
+ else
+ format.html { render :edit }
+ format.json { render json: @tem_test.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /tem_tests/1
+ # DELETE /tem_tests/1.json
+ def destroy
+ @tem_test.destroy
+ respond_to do |format|
+ format.html { redirect_to tem_tests_url, notice: 'Tem test was successfully destroyed.' }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_tem_test
+ @tem_test = TemTest.find(params[:id])
+ end
+
+ # Never trust parameters from the scary internet, only allow the white list through.
+ def tem_test_params
+ params.require(:tem_test).permit(:name, :email)
+ end
+end
diff --git a/app/controllers/tidings_controller.rb b/app/controllers/tidings_controller.rb
new file mode 100644
index 000000000..686f457f0
--- /dev/null
+++ b/app/controllers/tidings_controller.rb
@@ -0,0 +1,19 @@
+class TidingsController < ApplicationController
+ def index
+ tidings = current_user.tidings.order(created_at: :desc)
+
+ tiding_types =
+ case params[:type]
+ when 'notice' then 'System'
+ when 'apply' then 'Apply'
+ when 'course' then %w(HomeworkCommon Exercise Poll)
+ when 'project' then 'Project'
+ when 'interaction' then %w(Comment Mentioned Praise Fan)
+ end
+
+ tidings = tidings.where(tiding_type: tiding_types) if tiding_types.present?
+
+ @count = tidings.count
+ @tidings = paginate tidings
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/trial_applies_controller.rb b/app/controllers/trial_applies_controller.rb
new file mode 100644
index 000000000..f8454e557
--- /dev/null
+++ b/app/controllers/trial_applies_controller.rb
@@ -0,0 +1,15 @@
+class TrialAppliesController < ApplicationController
+
+ def create
+ Users::ApplyTrailService.call(current_user, create_params)
+ render_ok
+ rescue Users::ApplyTrailService::Error => ex
+ render_error(ex.message)
+ end
+
+ private
+
+ def create_params
+ params.permit(:phone, :code, :reason).merge(remote_ip: request.remote_ip)
+ end
+end
diff --git a/app/controllers/users/accounts_controller.rb b/app/controllers/users/accounts_controller.rb
new file mode 100644
index 000000000..e88ff4564
--- /dev/null
+++ b/app/controllers/users/accounts_controller.rb
@@ -0,0 +1,23 @@
+class Users::AccountsController < Users::BaseController
+ before_action :private_user_resources!
+
+ def show
+ end
+
+ def update
+ Users::UpdateAccountService.call(observed_user, update_params)
+
+ render 'show'
+ end
+
+ private
+
+ def observed_user
+ @_observed_user ||= (User.find_by_id(params[:id]) || User.find_by_login(params[:id]))
+ end
+
+ def update_params
+ params.permit(:nickname, :name, :show_realname, :gender, :location, :location_city,
+ :identity, :student_id, :technical_title, :school_id, :department_id)
+ end
+end
diff --git a/app/controllers/users/avatars_controller.rb b/app/controllers/users/avatars_controller.rb
new file mode 100644
index 000000000..6d161ba94
--- /dev/null
+++ b/app/controllers/users/avatars_controller.rb
@@ -0,0 +1,33 @@
+class Users::AvatarsController < Users::BaseAccountController
+ before_action :private_user_resources!
+ before_action :convert_base64_image!, only: [:update]
+
+ def update
+ Util.write_file(@image, avatar_path)
+
+ # 首次上传头像
+ RewardGradeService.call(observed_user, container_id: observed_user.id, container_type: 'Avatar', score: 100)
+
+ render_ok(avatar_url: avatar_url)
+ rescue StandardError => ex
+ logger_error(ex)
+ render_error('修改失败')
+ end
+
+ private
+
+ def convert_base64_image!
+ max_size = EduSetting.get('upload_avatar_max_size')
+ @image = Util.convert_base64_image(params[:image].to_s.strip, max_size: max_size)
+ rescue Base64ImageConverter::Error => ex
+ render_error(ex.message)
+ end
+
+ def avatar_path
+ ApplicationController.helpers.disk_filename(observed_user.class, observed_user.id)
+ end
+
+ def avatar_url
+ ApplicationController.helpers.url_to_avatar(observed_user)
+ end
+end
diff --git a/app/controllers/users/base_account_controller.rb b/app/controllers/users/base_account_controller.rb
new file mode 100644
index 000000000..b693eaab5
--- /dev/null
+++ b/app/controllers/users/base_account_controller.rb
@@ -0,0 +1,7 @@
+class Users::BaseAccountController < Users::BaseController
+ before_action :require_login
+
+ def observed_user
+ @_observed_user ||= (User.find_by_id(params[:account_id]) || User.find_by_login(params[:account_id]))
+ end
+end
diff --git a/app/controllers/users/base_controller.rb b/app/controllers/users/base_controller.rb
new file mode 100644
index 000000000..afc03ee13
--- /dev/null
+++ b/app/controllers/users/base_controller.rb
@@ -0,0 +1,42 @@
+class Users::BaseController < ApplicationController
+
+ before_action :check_observed_user_exists!
+
+ helper_method :observed_logged_user?, :observed_user
+
+ def observed_user
+ @_observed_user ||= (User.find_by_id(params[:user_id]) || User.find_by_login(params[:user_id]))
+ end
+
+ def observed_logged_user?
+ observed_user.id == User.current&.id
+ end
+
+ private
+
+ def check_observed_user_exists!
+ return if observed_user.present?
+ render_not_found
+ end
+
+ def private_user_resources!
+ require_login
+ return if current_user.admin? || observed_logged_user?
+
+ render_forbidden
+ end
+
+ def paginate(objs, **opts)
+ page = params[:page].to_i <= 0 ? 1 : params[:page].to_i
+ per_page = params[:per_page].to_i > 0 ? params[:per_page].to_i : 20
+
+ return Kaminari.paginate_array(objs).page(page).per(per_page) unless observed_logged_user? && opts[:special]
+
+ # note: 为实现第一页少一条记录,让前端放置新建入口
+ if page == 1
+ objs.limit(per_page - 1)
+ else
+ objs.limit(per_page).offset((page - 2) * per_page + per_page - 1)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/courses_controller.rb b/app/controllers/users/courses_controller.rb
new file mode 100644
index 000000000..c7e957e1a
--- /dev/null
+++ b/app/controllers/users/courses_controller.rb
@@ -0,0 +1,14 @@
+class Users::CoursesController < Users::BaseController
+ def index
+ courses = Users::CourseService.new(observed_user, query_params).call
+
+ @count = courses.count
+ @courses = paginate(courses.includes(teacher: { user_extension: :school }), special: true)
+ end
+
+ private
+
+ def query_params
+ params.permit(:category, :status, :sort_direction)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/email_binds_controller.rb b/app/controllers/users/email_binds_controller.rb
new file mode 100644
index 000000000..417ffc3b8
--- /dev/null
+++ b/app/controllers/users/email_binds_controller.rb
@@ -0,0 +1,16 @@
+class Users::EmailBindsController < Users::BaseAccountController
+ before_action :private_user_resources!
+
+ def create
+ Users::BindEmailService.call(observed_user, create_params)
+ render_ok
+ rescue Users::BindEmailService::Error => ex
+ render_error(ex.message)
+ end
+
+ private
+
+ def create_params
+ params.permit(:email, :code)
+ end
+end
diff --git a/app/controllers/users/experience_records_controller.rb b/app/controllers/users/experience_records_controller.rb
new file mode 100644
index 000000000..3207f7247
--- /dev/null
+++ b/app/controllers/users/experience_records_controller.rb
@@ -0,0 +1,10 @@
+class Users::ExperienceRecordsController < Users::BaseController
+ before_action :private_user_resources!
+
+ def show
+ experiences = observed_user.experiences.where('score > 0')
+
+ @count = experiences.count
+ @experience_records = paginate(experiences.order(created_at: :desc))
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/grade_records_controller.rb b/app/controllers/users/grade_records_controller.rb
new file mode 100644
index 000000000..c49e8732e
--- /dev/null
+++ b/app/controllers/users/grade_records_controller.rb
@@ -0,0 +1,18 @@
+class Users::GradeRecordsController < Users::BaseController
+ before_action :private_user_resources!
+
+ def show
+ grades = observed_user.grades
+
+ type = params[:type].to_s.strip
+ grades =
+ case type
+ when 'income' then grades.where('score > 0')
+ when 'cost' then grades.where('score < 0')
+ else grades
+ end
+
+ @count = grades.count
+ @grade_records = paginate(grades.order(created_at: :desc))
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/passwords_controller.rb b/app/controllers/users/passwords_controller.rb
new file mode 100644
index 000000000..fd4b4b634
--- /dev/null
+++ b/app/controllers/users/passwords_controller.rb
@@ -0,0 +1,14 @@
+class Users::PasswordsController < Users::BaseAccountController
+ def update
+ Users::UpdatePasswordService.call(observed_user, update_params)
+ render_ok
+ rescue Users::UpdatePasswordService::Error => ex
+ render_error(ex.message)
+ end
+
+ private
+
+ def update_params
+ params.permit(:password, :old_password)
+ end
+end
diff --git a/app/controllers/users/phone_binds_controller.rb b/app/controllers/users/phone_binds_controller.rb
new file mode 100644
index 000000000..873666341
--- /dev/null
+++ b/app/controllers/users/phone_binds_controller.rb
@@ -0,0 +1,16 @@
+class Users::PhoneBindsController < Users::BaseAccountController
+ before_action :private_user_resources!
+
+ def create
+ Users::BindPhoneService.call(observed_user, create_params)
+ render_ok
+ rescue Users::BindPhoneService::Error => ex
+ render_error(ex.message)
+ end
+
+ private
+
+ def create_params
+ params.permit(:phone, :code)
+ end
+end
diff --git a/app/controllers/users/projects_controller.rb b/app/controllers/users/projects_controller.rb
new file mode 100644
index 000000000..863b99b37
--- /dev/null
+++ b/app/controllers/users/projects_controller.rb
@@ -0,0 +1,14 @@
+class Users::ProjectsController < Users::BaseController
+ def index
+ projects = Users::ProjectService.new(observed_user, query_params).call
+
+ @count = projects.count
+ @projects = paginate(projects.includes(:project_score, owner: { user_extension: :school }), special: true)
+ end
+
+ private
+
+ def query_params
+ params.permit(:category, :status, :sort_direction)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/question_banks_controller.rb b/app/controllers/users/question_banks_controller.rb
new file mode 100644
index 000000000..0dc4a3f3f
--- /dev/null
+++ b/app/controllers/users/question_banks_controller.rb
@@ -0,0 +1,73 @@
+class Users::QuestionBanksController < Users::BaseController
+ before_action :check_query_params!
+ before_action :check_user_permission!
+
+ def index
+ service = Users::QuestionBankService.new(observed_user, query_params)
+ question_banks = service.call
+
+ @count = question_banks.count
+ @course_lists = service.course_lists
+ @question_banks = paginate(question_banks.includes(:user, :course_list), special: true)
+
+ load_question_banks_solve_count # for solve n + 1
+ end
+
+ private
+
+ def load_question_banks_solve_count
+ question_bank_ids = @question_banks.map(&:id)
+ @solve_count_map =
+ case params[:category]
+ when 'common', 'group' then
+ StudentWork.where(is_delete: false, work_status: [1, 2, 3]).joins(:homework_common)
+ .where(homework_commons: { homework_bank_id: question_bank_ids })
+ .group('homework_commons.homework_bank_id').count
+ when 'exercise' then
+ ExerciseUser.joins(:exercise)
+ .where(commit_status: 1, exercises: { exercise_bank_id: question_bank_ids })
+ .group('exercises.exercise_bank_id').count
+ when 'poll' then
+ PollUser.joins(:poll).where(polls: { exercise_bank_id: question_bank_ids })
+ .group('polls.exercise_bank_id').count
+ when 'gtask' then
+ GraduationWork.has_committed.joins(:graduation_task)
+ .where(graduation_tasks: { gtask_bank_id: question_bank_ids })
+ .group('graduation_tasks.gtask_bank_id').count
+ when 'gtopic' then
+ StudentGraduationTopic.joins(:graduation_topic)
+ .where(gtopic_banks: { gtopic_bank_id: question_bank_ids }).where('status != 0')
+ .group('gtopic_banks.gtopic_bank_id').count
+ end
+ end
+
+ def query_params
+ params.permit(:type, :category, :course_list_id, :sort_by, :sort_direction)
+ end
+
+ def check_query_params!
+ params[:type] = 'personal' if params[:type].blank? || !%w(personal publicly).include?(params[:type])
+
+ if params[:category].blank? || !%w(common group exercise poll gtask gtopic).include?(params[:category])
+ params[:category] = 'common'
+ end
+
+ if params[:sort_by].blank? || !%w(updated_at name contributor).include?(params[:sort_by])
+ params[:sort_by] = 'updated_at'
+ end
+
+ if params[:sort_direction].blank? || !%w(desc asc).include?(params[:sort_direction])
+ params[:sort_direction] = 'desc'
+ end
+ end
+
+ def check_user_permission!
+ return if User.current.admin? || (observed_logged_user? && read_question_bank_permission?)
+
+ render_forbidden
+ end
+
+ def read_question_bank_permission?
+ params[:type] == 'personal' ? User.current.is_teacher? : User.current.certification_teacher?
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/shixuns_controller.rb b/app/controllers/users/shixuns_controller.rb
new file mode 100644
index 000000000..c4c0d4c07
--- /dev/null
+++ b/app/controllers/users/shixuns_controller.rb
@@ -0,0 +1,14 @@
+class Users::ShixunsController < Users::BaseController
+ def index
+ shixuns = Users::ShixunService.new(observed_user, query_params).call
+
+ @count = shixuns.count
+ @shixuns = paginate(shixuns.includes(:first_tag_repertoire), special: true)
+ end
+
+ private
+
+ def query_params
+ params.permit(:category, :status, :sort_by, :sort_direction)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/subjects_controller.rb b/app/controllers/users/subjects_controller.rb
new file mode 100644
index 000000000..2a4a7975f
--- /dev/null
+++ b/app/controllers/users/subjects_controller.rb
@@ -0,0 +1,14 @@
+class Users::SubjectsController < Users::BaseController
+ def index
+ subjects = Users::SubjectService.new(observed_user, query_params).call
+
+ @count = subjects.count
+ @subjects = paginate(subjects.includes(:user, :repertoire), special: true)
+ end
+
+ private
+
+ def query_params
+ params.permit(:category, :status, :sort_direction)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users/watches_controller.rb b/app/controllers/users/watches_controller.rb
new file mode 100644
index 000000000..95a0b38cd
--- /dev/null
+++ b/app/controllers/users/watches_controller.rb
@@ -0,0 +1,28 @@
+class Users::WatchesController < Users::BaseController
+ before_action :require_login
+
+ def create
+ if observed_logged_user?
+ render_error('不能关注自己')
+ return
+ end
+
+ if current_user.watched?(observed_user)
+ render_ok
+ return
+ end
+
+ current_user.watch!(observed_user)
+ render_ok
+ end
+
+ def destroy
+ unless current_user.watched?(observed_user)
+ render_ok
+ return
+ end
+
+ current_user.unwatch!(observed_user)
+ render_ok
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
new file mode 100644
index 000000000..dc68f47c6
--- /dev/null
+++ b/app/controllers/users_controller.rb
@@ -0,0 +1,136 @@
+class UsersController < ApplicationController
+
+ before_action :load_user, only: [:show, :homepage_info]
+ before_action :check_user_exist, only: [:show, :homepage_info]
+
+ def show;end
+
+ def update
+ @user = User.find params[:id]
+ @user.update!(user_params)
+ render_ok
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception(e.message)
+ raise ActiveRecord::Rollback
+ end
+
+ # 贴吧获取用户信接口
+ def get_user_info
+ begin
+ @user = current_user
+ # TODO 等消息上线再打开注释
+ #@tidding_count = unviewed_tiddings(current_user) if current_user.present?
+ @course =
+ if params[:course_id]
+ Course.find params[:course_id]
+ elsif params[:board_id]
+ Board.find(params[:board_id]).course
+ elsif params[:graduation_topic_id]
+ GraduationTopic.find(params[:graduation_topic_id]).course
+ elsif params[:graduation_group_id]
+ GraduationGroup.find(params[:graduation_group_id]).course
+ elsif params[:graduation_work_id]
+ GraduationWork.find(params[:graduation_work_id]).course
+ elsif params[:graduation_task_id]
+ GraduationTask.find(params[:graduation_task_id]).course
+ elsif params[:poll_id]
+ Poll.find(params[:poll_id]).course
+ elsif params[:attachment_id]
+ Attachment.find(params[:attachment_id]).course
+ end
+ @course_identity = current_user.course_identity(@course) if @course
+ rescue Exception => e
+ missing_template
+ end
+
+ end
+
+ def attachment_show
+ file_name = params[:file_name]
+ path = params[:path]
+ send_file "#{path}/#{file_name}", :filename => "#{file_name}",
+ :type => 'game',
+ :disposition => 'attachment' #inline can open in browser
+ end
+
+ # Redo: 消息总数缓存
+ def get_navigation_info
+ @old_domain = edu_setting('old_edu_host')
+ @user = current_user
+ # 新消息数
+ @new_message = @user.tidings.where("created_at > '#{@user.click_time}'").count > 0 || @user.private_messages.where("created_at > '#{@user.click_time}'").count > 0
+
+ @user_url = "#{@old_domain}/users/#{@user.login}"
+ @career = Career.where(status: true).order("created_at asc").pluck(:id, :name)
+ ec_user = EcSchoolUser.where(:user_id => current_user.id).first
+ @auth = ec_user ? "#{@old_domain}/ecs/department?school_id=#{ec_user.school_id}" : nil
+ end
+
+ # 用户回复功能
+ def reply_message
+ @message = JournalsForMessage.new(reply_message_params)
+ @message.user_id = current_user.id
+ @message.save!
+ #normal_status("回复成功")
+ end
+
+ # 搜索用户具有管理员角色的项目
+ def search_user_projects
+ condition = '%%'
+ condition = "%#{params[:search].strip}%".gsub(" ","") if !params[:search].blank?
+
+ project_ids = Project.find_by_sql("SELECT p.id FROM projects p, members m, member_roles mr WHERE m.project_id = p.id
+ AND m.id=mr.member_id AND mr.role_id = 3 AND m.user_id=#{current_user.id} AND p.status != 9 and
+ p.name like '#{condition}'")
+ @projects = Project.where(id: project_ids.pluck(:id))
+ end
+
+ # 个人主页信息
+ def homepage_info;end
+
+ def brief_introduction
+ content = params[:content].to_s.strip
+ if content.blank?
+ render_error('内容不能为空')
+ return
+ end
+
+ current_user.user_extension.update!(brief_introduction: content)
+
+ render_ok
+ end
+
+ def attendance
+ attendance = Users::AttendanceService.call(current_user)
+ render_ok(grade: current_user.grade, next_gold: attendance.next_gold)
+ rescue Users::AttendanceService::Error => ex
+ render_error(ex.message)
+ end
+
+ private
+ def load_user
+ @user = User.find_by_login(params[:id]) || User.find_by(id: params[:id])
+ end
+
+ def user_params
+ params.require(:user).permit(:nickname, :lastname, :show_realname,
+ user_extension_attributes: [
+ :gender, :location, :location_city,
+ :occupation, :technical_title,
+ :school_id, :department_id]
+ )
+ end
+
+ def reply_message_params
+ normal_status(-1, "参数不对") if params[:journals_for_message][:jour_type].nil? || params[:journals_for_message][:jour_id].nil? ||
+ params[:journals_for_message][:notes].nil? || params[:journals_for_message][:reply_id].nil?
+ params.require(:journals_for_message).permit(:jour_type, :jour_id, :notes, :m_parent_id, :reply_id)
+ end
+
+ def check_user_exist
+ return if @user.present?
+ render_not_found
+ end
+
+end
diff --git a/app/controllers/zips_controller.rb b/app/controllers/zips_controller.rb
new file mode 100644
index 000000000..89f473499
--- /dev/null
+++ b/app/controllers/zips_controller.rb
@@ -0,0 +1,76 @@
+class ZipsController < ApplicationController
+ before_action :require_login
+ before_action :load_homework, only: [:shixun_report]
+ before_action :get_exercise, only: [:export_exercises]
+
+ before_action :require_admin_or_teacher
+
+ def shixun_report
+ student_work_ids = Array.wrap(params[:student_work_ids])
+
+ service = BatchExportShixunReportService.new(@homework, student_work_ids)
+
+ filename = filename_for_content_disposition(service.filename)
+ send_file service.zip, filename: filename, type: 'application/zip'
+ rescue BatchExportShixunReportService::Error => ex
+ normal_status(-1, ex.message)
+ end
+
+ def export_exercises
+ exercises = ExportExercisesService.new(@exercise,@ex_users)
+
+ file_name = filename_for_content_disposition(exercises.filename)
+ send_file exercises.ex_zip, filename: file_name, type: 'application/zip'
+ rescue Exception => e
+ normal_status(-1, e.message)
+ end
+
+ private
+
+ def filename_for_content_disposition(name)
+ request.env['HTTP_USER_AGENT'] =~ %r{MSIE|Trident|Edge} ? ERB::Util.url_encode(name) : name
+ end
+
+ def require_admin_or_teacher
+ return if current_user.teacher_or_admin?(@course)
+ normal_status(403, '')
+ end
+
+ def get_exercise
+ ActiveRecord::Base.transaction do
+ begin
+ @exercise = Exercise.find_by(id:params[:exercise_id])
+ group_id = params[:exercise_group_id]
+ if @exercise.blank?
+ normal_status(-1,"试卷不存在")
+ else
+ @course = @exercise.course
+
+ default_ex_users = @exercise.all_exercise_users(current_user.id).exercise_user_committed
+ default_ex_users_size = default_ex_users.size
+ @ex_users = default_ex_users #仅导出已提交的,截止后则是全部为提交的。
+ #可以分班选择
+ if group_id.present?
+ exercise_students = @course.students.course_find_by_ids("course_group_id",group_id) # 试卷所分班的全部人数
+ user_ids = exercise_students.pluck(:user_id).reject(&:blank?)
+ @ex_users = @ex_users.exercise_commit_users(user_ids)
+ end
+ # @ex_users = @ex_users.first(200)
+ if default_ex_users_size == 0
+ normal_status(-1,"导出失败,暂时没有已提交的学生")
+ elsif default_ex_users_size > 200
+ normal_status(-1,"导出数量超过200,请分班导出或联系网站管理员导出")
+ end
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("导出失败!")
+ end
+ end
+ end
+
+ def load_homework
+ @homework = HomeworkCommon.find(params[:homework_common_id])
+ @course = @homework.course
+ end
+end
diff --git a/app/decorators/course_decorator.rb b/app/decorators/course_decorator.rb
new file mode 100644
index 000000000..9c76b058a
--- /dev/null
+++ b/app/decorators/course_decorator.rb
@@ -0,0 +1,5 @@
+module CourseDecorator
+ def can_visited?
+ is_public == 1 || User.current.admin? || User.current.member_of_course?(self)
+ end
+end
\ No newline at end of file
diff --git a/app/decorators/ec_course_target_decorator.rb b/app/decorators/ec_course_target_decorator.rb
new file mode 100644
index 000000000..2965a8381
--- /dev/null
+++ b/app/decorators/ec_course_target_decorator.rb
@@ -0,0 +1,2 @@
+module EcCourseTargetDecorator
+end
\ No newline at end of file
diff --git a/app/decorators/experience_decorator.rb b/app/decorators/experience_decorator.rb
new file mode 100644
index 000000000..f50f479d7
--- /dev/null
+++ b/app/decorators/experience_decorator.rb
@@ -0,0 +1,16 @@
+module ExperienceDecorator
+ def container_type_text
+ I18n.t("experience.container_type.#{container_type.to_s.underscore}")
+ end
+
+ def content
+ case container_type.to_s.underscore
+ when 'game' then
+ game = Game.find_by(id: container_id)
+ game.present? ? "通过实训“#{game.challenge.shixun.name}”的第#{game.challenge.position}关获得的奖励" : ''
+ when 'shixun_publish' then
+ shixun = Shixun.find_by(id: container_id)
+ shixun.present? ? "发布实训“#{shixun.name}”获得的奖励" : ''
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/decorators/grade_decorator.rb b/app/decorators/grade_decorator.rb
new file mode 100644
index 000000000..ffffa11c9
--- /dev/null
+++ b/app/decorators/grade_decorator.rb
@@ -0,0 +1,36 @@
+module GradeDecorator
+ def container_type_text
+ I18n.t("grade.container_type.#{container_type.to_s.underscore}")
+ end
+
+ def content
+ case container_type.to_s.underscore
+ when 'avatar' then '用户首次上传头像获得的奖励'
+ when 'phone' then '用户首次绑定手机号码获得的奖励'
+ when 'mail' then '用户首次绑定邮箱获得的奖励'
+ when 'attendance' then '用户每天签到获得的奖励'
+ when 'account' then '新用户首次填写基本资料获得的奖励'
+ when 'memo' then '发布的评论或者帖子获得平台奖励'
+ when 'discusses' then '发布的评论获得平台奖励'
+ when 'star' then '用户给实训评分获得的随机奖励'
+ when 'feedback' then '反馈的问题获得平台奖励'
+ when 'authentication' then '用户首次完成实名认证获得的奖励'
+ when 'professional' then '用户首次完成职业认证获得的奖励'
+ when 'answer' then
+ game = Game.find_by(id: container_id)
+ game.present? ? "查看实训“#{game.challenge.shixun.name}”第#{game.challenge.position}关的参考答案消耗的金币" : ''
+ when 'game' then
+ game = Game.find_by(id: container_id)
+ game.present? ? "通过实训“#{game.challenge.shixun.name}”的第#{game.challenge.position}关获得的奖励" : ''
+ when 'test_set' then
+ game = Game.find_by(id: container_id)
+ game.present? ? "查看实训“#{game.challenge.shixun.name}”的第#{game.challenge.position}关的隐藏测试集消耗的金币" : ''
+ when 'shixun_publish' then
+ shixun = Shixun.find_by(id: container_id)
+ shixun.present? ? "发布实训“#{shixun.name}”获得的奖励" : ''
+ when 'check_ta_answer' then
+ game = Game.find_by(id: container_id)
+ game.present? ? "查看实训“#{game.challenge.shixun.name}”第#{game.challenge.position}关的TA人解答消耗的金币" : ''
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/decorators/project_decorator.rb b/app/decorators/project_decorator.rb
new file mode 100644
index 000000000..9cde1311e
--- /dev/null
+++ b/app/decorators/project_decorator.rb
@@ -0,0 +1,5 @@
+module ProjectDecorator
+ def can_visited?
+ is_public? || User.current.admin? || member?(User.current)
+ end
+end
diff --git a/app/decorators/shixun_decorator.rb b/app/decorators/shixun_decorator.rb
new file mode 100644
index 000000000..50e2e27eb
--- /dev/null
+++ b/app/decorators/shixun_decorator.rb
@@ -0,0 +1,9 @@
+module ShixunDecorator
+ def finished_challenges_count(user)
+ Game.joins(:myshixun).where(user_id: user.id, status: 2, myshixuns: { shixun_id: id }).count
+ end
+
+ def human_status
+ I18n.t("shixun.status.#{status}")
+ end
+end
diff --git a/app/decorators/subject_decorator.rb b/app/decorators/subject_decorator.rb
new file mode 100644
index 000000000..7ba3277f7
--- /dev/null
+++ b/app/decorators/subject_decorator.rb
@@ -0,0 +1,5 @@
+module SubjectDecorator
+ def can_visited?
+ published? || User.current.admin? || member?(User.current)
+ end
+end
\ No newline at end of file
diff --git a/app/decorators/tiding_decorator.rb b/app/decorators/tiding_decorator.rb
new file mode 100644
index 000000000..a9faeae84
--- /dev/null
+++ b/app/decorators/tiding_decorator.rb
@@ -0,0 +1,343 @@
+module TidingDecorator
+ def content
+ method_name = "#{container_type.underscore}_content"
+ respond_to?(method_name) ? send(method_name) : ''
+ end
+
+ def how_long_time
+ time_from_now(created_at)
+ end
+
+ # 组装国际化路径:locale_format('Apply', 1) ==> tiding.ApplyUserAuthentication.Apply.1_end
+ def locale_format(*arr)
+ arr.unshift("tiding.#{container_type}").join('.') << '_end'
+ end
+
+ def message_content_helper(msg)
+ msg = (strip_html msg).strip
+ msg = msg.gsub(/\s+/, ' ')
+ if msg.gsub(' ', '') == ''
+ msg = "[非文本消息]"
+ end
+ msg
+ end
+
+ def strip_html(text, len = 0, suffix = "...")
+ str = ""
+ if !text.nil? && text.length > 0
+ str = text.gsub(/<\/?.*?>/, '').strip
+ str = str.gsub(/ */, ' ')
+
+ if len > 0 && str.length > len
+ str = str[0, len] + suffix
+ elsif len > 0 && str.length <= len
+ str = str
+ end
+ end
+ str
+ end
+
+ # ================ 各种类消息内容方法 ================
+ def apply_user_authentication_content
+ return if trigger_user_id.zero?
+
+ if tiding_type == 'Apply'
+ str1, str2 = if container.auth_type == 1
+ [trigger_user.show_real_name, trigger_user.ID_number]
+ elsif container.auth_type == 2
+ ue = trigger_user.user_extension
+ [[ue.school&.name, ue.department&.name].join('_'), ue.identity_text]
+ end
+ I18n.t(locale_format(tiding_type, container.auth_type)) % [str1, str2]
+ elsif tiding_type == 'System'
+ I18n.t(locale_format(tiding_type, "#{container.auth_type}_#{status}"), reason: container.try(:remarks))
+ end
+ end
+
+ def cancel_user_authentication_content
+ I18n.t(locale_format) % [user.show_real_name, user.ID_number]
+ end
+
+ def cancel_user_pro_certification_content
+ ue = user.user_extension
+ I18n.t(locale_format) % [[ue.school&.name, ue.department&.name].join('_'), ue.identity_text]
+ end
+
+ def join_course_content
+ I18n.t(locale_format(extra)) % Course.find_by(id: container_id)&.name
+ end
+
+ def deal_course_content
+ name = Course.find_by(id: container_id)&.name
+ I18n.t(locale_format("#{extra}_#{status}")) % name
+ end
+
+ def student_join_course_content
+ I18n.t(locale_format) % Course.find_by(id: container_id)&.name
+ end
+
+ def teacher_join_course_content
+ name = Course.find_by(id: container_id)&.name
+ I18n.t(locale_format extra) % [user.show_real_name, name]
+ end
+
+ def apply_add_department_content
+ name = container.name
+ second_name = School.find_by_id(container.school_id).try(:name)
+ if tiding_type == 'Apply'
+ I18n.t(locale_format(tiding_type)) % [name, second_name]
+ elsif status == 2
+ I18n.t(locale_format(tiding_type, "#{status}_#{extra.nil?}"), reason: extra) % [name, second_name]
+ else
+ I18n.t(locale_format(tiding_type, status)) % [name, second_name]
+ end
+ end
+
+ def apply_add_schools_content
+ name = container.name
+ if tiding_type == 'Apply'
+ I18n.t(locale_format(tiding_type)) % name
+ elsif status == 2
+ I18n.t(locale_format(tiding_type, "#{status}_#{extra.nil?}"), reason: extra) % name
+ else
+ I18n.t(locale_format(tiding_type, status)) % name
+ end
+ end
+
+ def apply_action_content
+ name = case parent_container_type
+ when 'ApplyShixun' then Shixun.find_by(id: parent_container_id)&.name
+ when 'ApplySubject' then Subject.find_by(id: parent_container_id)&.name
+ when 'TrialAuthorization' then nil
+ end
+
+ if tiding_type == 'System'
+ I18n.t(locale_format(parent_container_type, tiding_type, status), name: name, reason: container.try(:reason))
+ elsif tiding_type == 'Apply'
+ I18n.t(locale_format(parent_container_type, tiding_type), name: name)
+ end
+ end
+
+ def course_content
+ I18n.t(locale_format) % container.name
+ end
+
+ def shixun_content
+ I18n.t(locale_format) % container.name
+ end
+
+ def subject_content
+ I18n.t(locale_format) % container.name
+ end
+
+ def archive_course_content
+ I18n.t(locale_format) % Course.find_by(id: container_id)&.name
+ end
+
+ def journals_for_message_content
+ format_str =
+ if tiding_type == 'Mentioned'
+ locale_format(tiding_type)
+ elsif parent_container_type == 'Principal'
+ if container.m_parent_id.present?
+ locale_format(parent_container_type, "#{container.m_parent_id.present?}_#{container.parent.try(:m_parent_id).present?}")
+ else
+ locale_format(parent_container_type, "#{container.m_parent_id.present?}_#{container.private.zero?}")
+ end
+ else
+ locale_format(parent_container_type, container.m_parent_id.present?)
+ end
+
+ I18n.t(format_str) % message_content_helper(container.notes)
+ end
+
+ def message_content
+ if tiding_type == 'Mentioned'
+ I18n.t(locale_format(tiding_type)) % message_content_helper(container.content)
+ elsif container.parent.present?
+ format_str = locale_format("#{true}_#{container.parent_id == container.root_id}")
+ I18n.t(format_str) % message_content_helper(container.content)
+ else
+ I18n.t(locale_format(false)) % message_content_helper(container.subject)
+ end
+ end
+
+ def memo_content
+ if tiding_type == 'Mentioned'
+ I18n.t(locale_format(tiding_type)) % message_content_helper(container.content)
+ elsif container.parent.present?
+ format_str = locale_format("#{true}_#{container.parent_id == container.root_id}")
+ I18n.t(format_str) % message_content_helper(container.content)
+ else
+ I18n.t(locale_format(false)) % message_content_helper(container.subject)
+ end
+ end
+
+ def watcher_content
+ I18n.t(locale_format)
+ end
+
+ def praise_tread_content
+ case parent_container_type
+ when 'Challenge' then
+ if container
+ format_str = I18n.t(locale_format(parent_container_type, container.praise_or_tread))
+ format_str % [parent_container.shixun.name, parent_container.position]
+ end
+ when 'Memo', 'Message' then
+ message = parent_container.parent_id.present? ? message_content_helper(parent_container.content) : parent_container.subject
+ I18n.t(locale_format(parent_container_type, parent_container.parent_id.present?)) % message
+ when 'HomeworkCommon' then
+ I18n.t(locale_format(parent_container_type)) % parent_container.name
+ when 'JournalsForMessage' then
+ i18n_url = locale_format(parent_container_type, parent_container.jour_type == 'Principal' && parent_container.m_parent_id.blank?)
+ I18n.t(i18n_url) % message_content_helper(parent_container.notes)
+ when 'Discuss' then
+ I18n.t(locale_format(parent_container_type)) % message_content_helper(parent_container.content)
+ when 'Issue' then
+ I18n.t(locale_format(parent_container_type)) % parent_container.subject
+ when 'Journal' then
+ message = object.notes.present? ? ':' + message_content_helper(parent_container.notes) : ''
+ I18n.t(locale_format(parent_container_type)) % message
+ end
+ end
+
+ def discuss_content
+ I18n.t(locale_format(container.parent_id.present?)) % message_content_helper(container.content)
+ end
+
+ def grade_content
+ case parent_container_type
+ when 'Answer' then
+ game = Game.find_by(id: parent_container_id)
+ if game.present?
+ format_str = I18n.t(locale_format(parent_container_type, true))
+ format_str % [game.challenge.shixun.name, game.challenge.position, container.score]
+ else
+ # 之所以这样处理,是因为消息的类型是不能和实体绑定,没有关联删除
+ I18n.t(locale_format(parent_container_type, false)) % container.score
+ end
+ when 'Game' then
+ game = Game.find_by(id: parent_container_id)
+ if game.present?
+ format_str = I18n.t(locale_format(parent_container_type))
+ format_str % [game.challenge.shixun.name, game.challenge.position, container.score]
+ end
+ when 'testSet' then
+ position = Game.find_by(id: parent_container_id)&.challenge&.position || '--'
+ I18n.t(locale_format(parent_container_type)) % [position, container.score]
+ when 'shixunPublish' then
+ name = Shixun.find_by(id: parent_container_id)&.name || '---'
+ I18n.t(locale_format(parent_container_type)) % [name, container.score]
+ else
+ I18n.t(locale_format(parent_container_type)) % container.score
+ end
+ end
+
+ def join_project_content
+ project = Project.find_by(id: container_id)
+ I18n.t(locale_format(extra)) % project.name
+ end
+
+ def deal_project_content
+ project = Project.find_by(id: container_id)
+ I18n.t(locale_format("#{extra}_#{status}")) % project.name
+ end
+
+ def manager_join_project_content
+ project = Project.find_by(id: container_id)
+ I18n.t(locale_format(extra)) % [user&.show_real_name, project.name]
+ end
+
+ def reporter_join_project_content
+ project = Project.find_by(id: container_id)
+ I18n.t(locale_format) % project.name
+ end
+
+ def journal_content
+ case tiding_type
+ when 'Mentioned' then
+ I18n.t(locale_format(tiding_type)) % message_content_helper(container.notes)
+ when 'Comment' then
+ I18n.t(locale_format(tiding_type, container.parent.present?)) % message_content_helper(container.notes)
+ else
+ I18n.t(locale_format) % container.issue.subject
+ end
+ end
+
+ def issue_content
+ I18n.t(locale_format) % container.subject
+ end
+
+ def pull_request_content
+ if tiding_type == 'Apply'
+ I18n.t(locale_format(tiding_type)) % container.try(:title)
+ else
+ I18n.t(locale_format(status)) % container.try(:title)
+ end
+ end
+
+ def send_message_content
+ data = (JSON.parse(extra) rescue extra)
+ if data.is_a?(String)
+ I18n.t(locale_format('old')) % extra
+ else
+ I18n.t(locale_format('new')) % [data['language'], data['runtime'], data['run_method']]
+ end
+ end
+
+ def poll_content
+ I18n.t(locale_format(parent_container_type)) % container.polls_name
+ end
+
+ def exercise_content
+ I18n.t(locale_format(parent_container_type)) % container.exercise_name
+ end
+
+ def student_graduation_topic_content
+ I18n.t(locale_format) % container.graduation_topic.try(:name)
+ end
+
+ def deal_student_topic_select_content
+ I18n.t(locale_format(status)) % GraduationTopic.find_by(id: parent_container_id).try(:name)
+ end
+
+ def graduation_task_content
+ I18n.t(locale_format(parent_container_type)) % container.name
+ end
+
+ def graduation_work_content
+ I18n.t(locale_format(extra.nil?)) % container.graduation_task.try(:name)
+ end
+
+ def graduation_work_score_content
+ I18n.t(locale_format) % container.graduation_work.graduation_task.try(:name)
+ end
+
+ def homework_common_content
+ I18n.t(locale_format(parent_container_type), name: container.name, reason: extra)
+ end
+
+ def student_work_content
+ I18n.t(locale_format(extra.nil?)) % container.homework_common.try(:name)
+ end
+
+ def student_works_score_content
+ I18n.t(locale_format(extra)) % container.student_work.homework_common.try(:name)
+ end
+
+ def challenge_work_score_content
+ work = StudentWork.find_by(id: parent_container_id)
+ return if work.blank?
+
+ if parent_container_type == 'StudentWork'
+ I18n.t(locale_format(parent_container_type, tiding_type)) % work.homework_common.try(:name)
+ elsif parent_container_type == 'UserAppealResult' || parent_container_type == 'AppealResult'
+ I18n.t(locale_format(parent_container_type, status)) % work.homework_common.try(:name)
+ end
+ end
+
+ def department_content
+ I18n.t(locale_format) % [container.try(:name), container.try(:school)&.name]
+ end
+end
diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb
new file mode 100644
index 000000000..0ee9a4b43
--- /dev/null
+++ b/app/decorators/user_decorator.rb
@@ -0,0 +1,89 @@
+module UserDecorator
+ def logged_user?
+ User.current.id == id
+ end
+
+ def name
+ [lastname, firstname].join('')
+ end
+
+ # ---------- homepage helper ---------
+ def homepage_name
+ logged_user? ? real_name : full_name
+ end
+
+ # 关注数
+ def follow_count
+ User.watched_by(id).count
+ end
+
+ # 粉丝数
+ def fan_count
+ watchers.count
+ end
+
+ # 是否绑定邮箱
+ def email_binded?
+ mail.present?
+ end
+
+ # 学院的url标识
+ def college_identifier
+
+ Department.find_by_id(department_members.pluck(:department_id).first)&.identifier
+ end
+
+ # 是否能申请试用
+ def can_apply_trial?
+ return false if certification == 1
+
+ apply = ApplyAction.order(created_at: :desc).find_by(user_id: id, container_type: 'TrialAuthorization')
+
+ apply && !apply.status.zero?
+ end
+
+ # 是否已经签到
+ def attendance_signed?
+ attendance = Attendance.find_by(user_id: id)
+
+ attendance && Util.days_between(Time.zone.now, attendance.created_at).zero?
+ end
+
+ # 明日签到金币
+ def tomorrow_attendance_gold
+ Attendance.find_by(user_id: id)&.next_gold
+ end
+
+ # ----------- 账号管理 -------------
+ def authentication_status
+ if authentication?
+ 'certified'
+ elsif process_real_name_apply.present?
+ 'applying'
+ else
+ 'uncertified'
+ end
+ end
+
+ def professional_certification_status
+ if professional_certification?
+ 'certified'
+ elsif process_professional_apply.present?
+ 'applying'
+ else
+ 'uncertified'
+ end
+ end
+
+ def base_info_completed?
+ user_columns = %i[nickname lastname]
+ user_extension_columns = %i[gender location location_city identity school_id department]
+
+ user_columns.all? { |column| public_send(column).present? } &&
+ user_extension_columns.all? { |column| user_extension.send(column).present? }
+ end
+
+ def all_certified?
+ authentication? && professional_certification?
+ end
+end
\ No newline at end of file
diff --git a/app/forms/apply_shixun_mirror_form.rb b/app/forms/apply_shixun_mirror_form.rb
new file mode 100644
index 000000000..4f6b738e2
--- /dev/null
+++ b/app/forms/apply_shixun_mirror_form.rb
@@ -0,0 +1,27 @@
+class ApplyShixunMirrorForm
+ include ActiveModel::Model
+
+ attr_accessor :language, :runtime, :run_method, :attachment_id
+
+ validates :language, presence: true
+ validates :runtime, presence: true
+ validates :run_method, presence: true
+ validates :attachment_id, presence: true, numericality: { only_integer: true }
+
+ validate :ensure_attachment_presence
+ def ensure_attachment_presence
+ return unless attachment_id
+
+ if attachment.blank?
+ errors.add(:attachment_id, :attachment_not_exist)
+ end
+ end
+
+ def attachment
+ @attachment ||= Attachment.find_by_id(attachment_id)
+ end
+
+ def to_json
+ { language: language, runtime: runtime, run_method: run_method, attachment_id: attachment_id }.to_json
+ end
+end
\ No newline at end of file
diff --git a/app/forms/ecs/create_course_achievement_methods_form.rb b/app/forms/ecs/create_course_achievement_methods_form.rb
new file mode 100644
index 000000000..2c9ceb3c9
--- /dev/null
+++ b/app/forms/ecs/create_course_achievement_methods_form.rb
@@ -0,0 +1,48 @@
+class Ecs::CreateCourseAchievementMethodsForm
+ include ActiveModel::Model
+
+ attr_accessor :ec_course, :course_achievement_methods
+
+ validates :course_achievement_methods, presence: true
+
+ validate :check_total_percentage
+ def check_total_percentage
+ total_percentage = course_achievement_methods.sum { |item| item[:percentage].to_i }
+ if total_percentage > 100 || total_percentage < 0
+ errors.add(:course_achievement_methods, :percentage_error)
+ end
+ end
+
+ def validate!
+ super
+ return unless errors.blank?
+ course_achievement_methods.each { |item| SubForm.new(item.merge(ec_course: ec_course)).validate! }
+ end
+
+ class SubForm
+ include ActiveModel::Model
+
+ attr_accessor :ec_course
+ attr_accessor :id, :course_evaluation_id, :course_evaluation_subitem_ids, :score, :percentage
+
+ validates :course_evaluation_id, presence: true
+ validates :course_evaluation_subitem_ids, presence: true
+ validates :score, presence: true, numericality: { only_integer: true }
+ validates :percentage, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }
+
+ validate :check_course_evaluation_id_valid
+ def check_course_evaluation_id_valid
+ return if ec_course.ec_course_evaluations.exists?(id: course_evaluation_id)
+
+ errors.add(:course_evaluation_id, :record_not_exist)
+ end
+
+ validate :check_course_evaluation_subitem_ids_valid
+ def check_course_evaluation_subitem_ids_valid
+ relations = ec_course.ec_course_evaluation_subitems.where(id: course_evaluation_subitem_ids)
+ return if relations.count == course_evaluation_subitem_ids.size
+
+ errors.add(:course_evaluation_subitem_ids, :record_not_exist)
+ end
+ end
+end
diff --git a/app/forms/ecs/save_course_evaluation_form.rb b/app/forms/ecs/save_course_evaluation_form.rb
new file mode 100644
index 000000000..4e4c4982b
--- /dev/null
+++ b/app/forms/ecs/save_course_evaluation_form.rb
@@ -0,0 +1,22 @@
+class Ecs::SaveCourseEvaluationForm
+ include ActiveModel::Model
+
+ attr_accessor :name, :evaluation_count, :status, :course_evaluation_subitems
+
+ validates :name, presence: true
+ validates :evaluation_count, presence: true, numericality: { only_integer: true }
+ validates :status, presence: true, inclusion: { in: %w[partly totality] }
+
+ validate :course_evaluation_subitems_validate
+ def course_evaluation_subitems_validate
+ if course_evaluation_subitems.blank?
+ errors.add(:course_evaluation_subitems, :blank)
+ return
+ end
+
+ if course_evaluation_subitems.any? { |item| item[:name].blank? }
+ errors.add(:course_evaluation_subitems, :name_blank)
+ return
+ end
+ end
+end
diff --git a/app/forms/ecs/save_graduation_course_support_form.rb b/app/forms/ecs/save_graduation_course_support_form.rb
new file mode 100644
index 000000000..75081fb21
--- /dev/null
+++ b/app/forms/ecs/save_graduation_course_support_form.rb
@@ -0,0 +1,25 @@
+class Ecs::SaveGraduationCourseSupportForm
+ include ActiveModel::Model
+
+ attr_accessor :course_supports
+
+ validates :course_supports, presence: true
+
+ validate :check_course_support_unique
+ def check_course_support_unique
+ uniq_size = course_supports.map { |support| support[:ec_course_id].to_i }.uniq.size
+
+ if uniq_size != course_supports.size
+ errors.add(:course_supports, :not_unique)
+ end
+ end
+
+ validate :check_weight_total
+ def check_weight_total
+ total = course_supports.sum { |item| item[:weights].to_f }
+
+ if total > 1
+ errors.add(:course_supports, :weights_too_big)
+ end
+ end
+end
diff --git a/app/forms/users/apply_trail_form.rb b/app/forms/users/apply_trail_form.rb
new file mode 100644
index 000000000..14da21d0d
--- /dev/null
+++ b/app/forms/users/apply_trail_form.rb
@@ -0,0 +1,16 @@
+class Users::ApplyTrailForm
+ include ActiveModel::Model
+
+ attr_accessor :user, :phone, :code, :reason
+
+ validates :reason, presence: true
+ validates :phone, presence: true, format: { with: CustomRegexp::PHONE }, unless: -> { user.phone_binded? }
+ validates :code, presence: true, unless: -> { user.phone_binded? }
+
+ validate :check_user_certification
+ def check_user_certification
+ return if user.certification != 1
+
+ errors.add(:user, :already_trial)
+ end
+end
\ No newline at end of file
diff --git a/app/forms/users/bind_email_form.rb b/app/forms/users/bind_email_form.rb
new file mode 100644
index 000000000..2972430fb
--- /dev/null
+++ b/app/forms/users/bind_email_form.rb
@@ -0,0 +1,8 @@
+class Users::BindEmailForm
+ include ActiveModel::Model
+
+ attr_accessor :email, :code
+
+ validates :email, presence: true#, format: { with: CustomRegexp::EMAIL }
+ validates :code, presence: true
+end
\ No newline at end of file
diff --git a/app/forms/users/bind_phone_form.rb b/app/forms/users/bind_phone_form.rb
new file mode 100644
index 000000000..46cec2d86
--- /dev/null
+++ b/app/forms/users/bind_phone_form.rb
@@ -0,0 +1,8 @@
+class Users::BindPhoneForm
+ include ActiveModel::Model
+
+ attr_accessor :phone, :code
+
+ validates :phone, presence: true, format: { with: CustomRegexp::PHONE }
+ validates :code, presence: true
+end
\ No newline at end of file
diff --git a/app/forms/users/update_account_form.rb b/app/forms/users/update_account_form.rb
new file mode 100644
index 000000000..68e7fb7bf
--- /dev/null
+++ b/app/forms/users/update_account_form.rb
@@ -0,0 +1,33 @@
+class Users::UpdateAccountForm
+ include ActiveModel::Model
+
+ attr_accessor :user
+ attr_accessor :nickname, :name, :show_realname, :gender, :location, :location_city,
+ :identity, :student_id, :technical_title, :school_id, :department_id
+
+ validates :nickname, presence: true
+ validates :name, presence: true
+ validates :gender, presence: true, numericality: { only_integer: true }, inclusion: { in: [0, 1] }
+ validates :location, presence: true
+ validates :location_city, presence: true
+ validates :identity, presence: true, numericality: { only_integer: true }, inclusion: { in: [0, 1, 2] }
+ validates :technical_title, presence: true, unless: -> { identity == 1 }
+ validates :student_id, presence: true, if: -> { identity == 1 }
+ validates :school_id, presence: true
+
+ validate :check_school_exist
+ def check_school_exist
+ return if school_id.blank?
+ unless School.exists?(id: school_id)
+ errors.add(:school_id, :not_exist)
+ end
+ end
+
+ validate :check_department_exist
+ def check_department_exist
+ return if department_id.blank?
+ unless Department.exists?(id: department_id)
+ errors.add(:department_id, :not_exist)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/forms/users/update_password_form.rb b/app/forms/users/update_password_form.rb
new file mode 100644
index 000000000..023caa40f
--- /dev/null
+++ b/app/forms/users/update_password_form.rb
@@ -0,0 +1,8 @@
+class Users::UpdatePasswordForm
+ include ActiveModel::Model
+
+ attr_accessor :password, :old_password
+
+ validates :password, presence: true
+ validates :old_password, presence: true
+end
\ No newline at end of file
diff --git a/app/forms/validate/user.rb b/app/forms/validate/user.rb
new file mode 100644
index 000000000..0f8f5e9ba
--- /dev/null
+++ b/app/forms/validate/user.rb
@@ -0,0 +1,10 @@
+module Validate
+ class User
+ include ActiveModel::Model
+
+ attr_accessor :nickname, :lastname
+
+ validates :nickname, presence: true, length: { maximum: 10 }
+ validates :lastname, presence: true
+ end
+end
diff --git a/app/forms/validate/user_extension.rb b/app/forms/validate/user_extension.rb
new file mode 100644
index 000000000..f302abb2c
--- /dev/null
+++ b/app/forms/validate/user_extension.rb
@@ -0,0 +1,9 @@
+module Validate
+ class UserExtension
+ include ActiveModel::Model
+ attr_accessor :location
+
+ validates :location, presence: true
+
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
new file mode 100644
index 000000000..ac50e78d3
--- /dev/null
+++ b/app/helpers/application_helper.rb
@@ -0,0 +1,337 @@
+# 所有的方法请按首字母的顺序依次列出
+module ApplicationHelper
+ include Educoder::I18n
+
+ ONE_MINUTE = 60 * 1000
+ ONE_HOUR = 60 * ONE_MINUTE
+ ONE_DAY = 24 * ONE_HOUR
+ ONE_MONTH = 30 * ONE_DAY
+
+ ONE_YEAR = 12 * ONE_MONTH
+
+ # 全局参数配置
+ def edu_setting name
+ EduSetting.find_by_name(name).try(:value)
+ end
+
+ def graduation_navigation graduation
+ graduation.class.to_s == "GraduationTopic" ? "毕设选题" : "毕设任务"
+ end
+
+ def graduation_navigation_id course
+ course.course_modules.find_by(module_type: "graduation").try(:id)
+ end
+
+ # 是否关注
+ # from_user_id为被关注的用户
+ def follow?(from_user_id, user_id)
+ Watcher.where(watchable_type: 'Principal', watchable_id: from_user_id, user_id: user_id).exists?
+ end
+
+ # git用户
+ def git_username(email)
+ User.find_by_mail(email)
+ end
+
+ # 不同的类型扩展不同的目录
+ def relative_path
+ "avatars"
+ end
+
+ def storage_path
+ File.join(Rails.root, "public", "images", relative_path)
+ end
+
+ # 推荐实训
+ def recommend_shixun(shixun)
+ tag_repertoire_id = shixun.tag_repertoires.first.present? ? shixun.tag_repertoires.first.try(:id) : 0
+ shixun_id = ShixunTagRepertoire.where("tag_repertoire_id = #{tag_repertoire_id} and
+ shixun_id != #{shixun.id}").pluck(:shixun_id)
+
+ shixun_id = shixun_id.blank? ? -1 : shixun_id.join(",")
+ Shixun.select([:id, :name, :user_id, :challenges_count, :myshixuns_count, :trainee, :identifier]).where("id
+ in(#{shixun_id}) or homepage_show =1").unhidden.order("myshixuns_count desc, homepage_show asc").limit(3)
+ end
+
+ # 相关推荐
+ def relation_path(shixun)
+ shixun.subjects.where(hidden: 0).limit(2)
+ end
+
+ # shixun开启挑战对应的行为名及url
+ def task_operation_url current_myshixun, shixun
+ url = "/shixuns/#{shixun.identifier}/shixun_exec"
+ name =
+ if current_myshixun.blank?
+ shixun.status == 0 ? "模拟实战" : "开启挑战"
+ elsif current_myshixun.status == 1
+ "查看实战"
+ else
+ "继续挑战"
+ end
+ [name, url]
+ end
+
+ # 获取当前时间
+ def time_from_now(time)
+ if String === time
+ time = Time.parse(time)
+ end
+
+ lastUpdateTime = time.to_i*1000
+
+ currentTime = Time.now.to_i*1000
+ timePassed = currentTime - lastUpdateTime
+ timeIntoFormat = 0
+ updateAtValue = ""
+ if timePassed < 0
+ updateAtValue = "刚刚"
+ elsif timePassed < ONE_MINUTE
+ updateAtValue = "1分钟前"
+ elsif timePassed < ONE_HOUR
+ timeIntoFormat = timePassed / ONE_MINUTE
+ updateAtValue = timeIntoFormat.to_s + "分钟前"
+ elsif (timePassed < ONE_DAY)
+ timeIntoFormat = timePassed / ONE_HOUR
+ updateAtValue = timeIntoFormat.to_s + "小时前"
+ elsif (timePassed < ONE_MONTH)
+ timeIntoFormat = timePassed / ONE_DAY
+ updateAtValue = timeIntoFormat.to_s + "天前"
+ elsif (timePassed < ONE_YEAR)
+ timeIntoFormat = timePassed / ONE_MONTH
+ updateAtValue = timeIntoFormat.to_s + "个月前"
+ else
+ timeIntoFormat = timePassed / ONE_YEAR
+ updateAtValue = timeIntoFormat.to_s + "年前"
+ end
+ updateAtValue
+ end
+
+ # 计算到结束还有多长时间 **天**小时**分
+ def how_much_time(time)
+ if time.nil?
+ ''
+ else
+ result = ((time - Time.now.to_i).to_i / (24*60*60)).to_s + " 天 "
+ result += (((time - Time.now.to_i).to_i % (24*60*60)) / (60*60)).to_s + " 小时 "
+ result + ((((time - Time.now.to_i).to_i % (24*60*60)) % (60*60)) / 60).to_s + " 分 "
+ end
+ end
+
+ def format_time(time)
+ time.present? ? time.strftime("%Y-%m-%d %H:%M") : ''
+ end
+
+ # 用户图像url,如果不存在的话,source为匿名用户,即默认使用匿名用户图像
+ def url_to_avatar(source)
+ if File.exist?(disk_filename(source.class, source.id))
+ if source.class.to_s == 'User'
+ File.join(relative_path, ["#{source.class}", "#{source.id}"])
+ else
+ File.join("/images/avatars", ["#{source.class}", "#{source.id}"])
+ end
+ elsif source.class.to_s == 'User'
+ str = source.user_extension.try(:gender).to_i == 0 ? "b" : "g"
+ File.join(relative_path, "#{source.class}", str)
+ elsif source.class.to_s == 'Subject'
+ File.join("images","educoder", "index", "subject", "subject#{rand(17)}.jpg")
+ elsif source.class.to_s == 'Shixun'
+ File.join("images","educoder", "index", "shixun", "shixun#{rand(23)}.jpg")
+ end
+ end
+
+ def disk_filename(source_type,source_id,image_file=nil)
+ File.join(storage_path, "#{source_type}", "#{source_id}")
+ end
+
+ def shixun_url_to_avatar(shixun)
+ if File.exist?(disk_filename(shixun.class, shixun.id))
+ File.join("/images/#{relative_path}", "#{shixun.class}", "#{shixun.id}")
+ else
+ File.join("educoder", "index", "shixun", "shixun#{rand(23)}.jpg")
+ end
+ end
+
+ # 选用实训的学校情况
+ def school_user_detail shixun
+ school_ids = shixun.myshixuns.joins("join user_extensions ue on myshixuns.user_id=ue.user_id").pluck(:school_id).uniq
+ school_names = School.where(id: school_ids[0..1]).pluck(:name)
+ school_size = school_ids.size
+ str = school_size > 0 ? "#{school_names.join("、")}等 #{school_size}所" : "0所"
+ end
+
+ # 普通/分组 作业作品状态数组
+ def student_work_status homework, user_id, course, work
+ status = []
+ homework_setting = homework.homework_group_setting user_id
+ work = work || StudentWork.create(homework_common_id: homework.id, user_id: user_id)
+ late_time = homework.late_time || course.end_date
+
+ if course.is_end && work && work.work_status > 0
+ status << "查看作品"
+ elsif !course.is_end
+ if homework_setting.publish_time && homework_setting.publish_time < Time.now
+ # 作业未截止时
+ if homework_setting.end_time > Time.now
+ if homework.homework_type == "group" && homework.homework_detail_group.base_on_project
+ if work.project_id.nil? || work.project_id == 0
+ status << "创建项目"
+ status << "关联项目"
+ elsif work.work_status == 0
+ status << "取消关联"
+ status << "提交作品"
+ else
+ status << "修改作品"
+ end
+ else
+ if work.work_status == 0
+ status << "提交作品"
+ else
+ status << "修改作品"
+ end
+ end
+
+ # 补交阶段
+ elsif homework.allow_late && (late_time.nil? || late_time > Time.now)
+ if homework.homework_type == "group" && homework.homework_detail_group.base_on_project
+ if work.project_id.nil? || work.project_id == 0
+ status << "创建项目"
+ status << "关联项目"
+ elsif work.work_status == 0
+ status << "取消关联"
+ status << "补交作品"
+ else
+ status << "补交附件"
+ status << "查看作品"
+ end
+ else
+ if work.work_status == 0
+ status << "补交作品"
+ else
+ status << "补交附件"
+ status << "查看作品"
+ end
+ end
+
+ # 匿评阶段
+ elsif work.work_status != 0
+ if homework.homework_detail_manual.comment_status == 3
+ work_ids = homework.student_works.has_committed.pluck(:id)
+ if StudentWorksEvaluationDistribution.where(student_work_id: work_ids, user_id: user_id).size > 0
+ status << "匿评作品"
+ end
+ end
+ status << "查看作品"
+ end
+ end
+ end
+ end
+
+ def commit_des_status work, homework
+ status = []
+ homework_setting = homework.homework_group_setting work.user_id
+
+ # 作业已发布且作业未截止(补交未截止)且提交了作品才能提交或修改总结
+ if homework_setting.publish_time && homework_setting.publish_time < Time.now && work.work_status > 0 &&
+ ((homework_setting.end_time && homework_setting.end_time > Time.now) ||
+ (homework.allow_late && homework.late_time && homework.late_time > Time.now))
+ work.description.present? ? status << "修改总结" : status << "提交总结"
+ end
+ end
+
+ def download_url attachment
+ attachment_path(attachment)
+ end
+
+ # 耗时:天、小时、分、秒
+ # 小于1分钟则不显示
+ def game_spend_time time
+ day = time / 86400
+ hour = time % (24*60*60) / (60*60)
+ min = time % (24*60*60) % (60*60) / 60
+ sec = time % (24*60*60) % (60*60) % 60
+ if day < 1
+ if hour < 1
+ if min < 1
+ if sec < 1
+ time = "--"
+ else
+ time = "#{sec}秒"
+ end
+ else
+ time = "#{min}分钟 #{sec}秒"
+ end
+ else
+ time = "#{hour}小时 #{min}分钟 #{sec}秒"
+ end
+ else
+ time = "#{day}天 #{hour}小时 #{min}分钟 #{sec}秒"
+ end
+ return time
+ end
+
+ def absolute_path(file_path)
+ File.join(edu_setting('attachment_folder'), file_path)
+ end
+
+ def local_path(file)
+ File.join(file.disk_directory, file.disk_filename)
+ end
+
+ def update_downloads(file)
+ file.update_attributes(:downloads => file.downloads + 1)
+ end
+
+ # 项目信息,
+ def project_info work, current_user, course_identity
+ project = work.project
+ if project
+ if project.status == 9
+ {id: -1, name: "#{project.name}(已删除)", title: "该项目已删除", author: project.creator, member_count: project.project_members.count}
+ else
+ project_score = project.project_score
+ if project.is_public || current_user.manager_of_project?(project) || course_identity < Course::STUDENT
+ {id: project.id, name: project.name, author: project.creator, member_count: project.project_members.count,
+ all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score,
+ attachment_score: project_score.attachment_score, message_score: project_score.message_score}
+ else
+ {id: -1, name: "#{project.name}", title: "该项目是私有的", author: project.creator, member_count: project.project_members.count,
+ all_score: project_score.all_score, code_score: project_score.code_score, issue_score: project_score.issue_score,
+ attachment_score: project_score.attachment_score, message_score: project_score.message_score}
+ end
+ end
+ else
+ {id: -1, name: "--", title: "--"}
+ end
+ end
+
+ def message_content(content)
+ content = (strip_html content).strip
+ content = content.gsub(/\s+/, " ")
+ if content.gsub(" ", "") == ""
+ content = "[非文本消息]"
+ end
+ content
+ end
+
+ def strip_export_title(content)
+ con_ = ""
+ if content.length > 0
+ con_ = strip_tags(content)
+ con_ = con_.gsub(/\r\n/,'').gsub(/ */, '').strip
+ end
+ con_
+ end
+
+ def pdf_load_sources(*arg)
+ arr = arg.map do |path|
+ content_tag(:script) do
+ File.open(Rails.root.join('public', path)).read.to_s.html_safe
+ end
+ end
+
+ raw arr.join('')
+ end
+end
+
+
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
new file mode 100644
index 000000000..e66bdaf6b
--- /dev/null
+++ b/app/helpers/boards_helper.rb
@@ -0,0 +1,2 @@
+module BoardsHelper
+end
diff --git a/app/helpers/challenges_helper.rb b/app/helpers/challenges_helper.rb
new file mode 100644
index 000000000..c6d05817d
--- /dev/null
+++ b/app/helpers/challenges_helper.rb
@@ -0,0 +1,7 @@
+module ChallengesHelper
+
+ def match_begin_symbol str
+ str.gsub(/\A\r/, "\r\r")
+ end
+
+end
diff --git a/app/helpers/course_groups_helper.rb b/app/helpers/course_groups_helper.rb
new file mode 100644
index 000000000..061c39dd5
--- /dev/null
+++ b/app/helpers/course_groups_helper.rb
@@ -0,0 +1,2 @@
+module CourseGroupsHelper
+end
diff --git a/app/helpers/course_modules_helper.rb b/app/helpers/course_modules_helper.rb
new file mode 100644
index 000000000..4de7a3826
--- /dev/null
+++ b/app/helpers/course_modules_helper.rb
@@ -0,0 +1,2 @@
+module CourseModulesHelper
+end
diff --git a/app/helpers/course_second_categories_helper.rb b/app/helpers/course_second_categories_helper.rb
new file mode 100644
index 000000000..7ed9aa1e9
--- /dev/null
+++ b/app/helpers/course_second_categories_helper.rb
@@ -0,0 +1,2 @@
+module CourseSecondCategoriesHelper
+end
diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb
new file mode 100644
index 000000000..8c88f02d0
--- /dev/null
+++ b/app/helpers/courses_helper.rb
@@ -0,0 +1,260 @@
+module CoursesHelper
+
+ # 是否有切换为学生的入口
+ def switch_student_role is_teacher, course, user
+ is_teacher && course.course_members.where(user_id: user.id, role: %i(STUDENT)).exists?
+ end
+
+ # 是否有切换为教师的入口
+ def switch_teacher_role is_student, course, user
+ is_student && course.course_members.where(user_id: user.id, role: %i(CREATOR PROFESSOR)).exists?
+ end
+
+ # 是否有切换为助教的入口
+ def switch_assistant_role is_student, course, user
+ is_student && course.course_members.where(user_id: user.id, role: %i(ASSISTANT_PROFESSOR)).exists?
+ end
+
+ # 课堂结束天数
+ def course_end_date end_date
+ if end_date.present?
+ curr = Time.new
+ date = ((Date.parse(end_date.to_s) - Date.parse(curr.to_s)).to_i)
+ date > 0 ? "#{date}天后" : ""
+ end
+ end
+
+ # 课堂模块的url
+ def module_url mod, course
+ return nil if mod.blank? or course.blank?
+ case mod.module_type
+ when "shixun_homework"
+ "/courses/#{course.id}/shixun_homeworks/#{mod.id}"
+ when "common_homework"
+ "/courses/#{course.id}/common_homeworks/#{mod.id}"
+ when "group_homework"
+ "/courses/#{course.id}/group_homeworks/#{mod.id}"
+ when "graduation"
+ "/courses/#{course.id}/graduation_topics/#{mod.id}"
+ when "exercise"
+ "/courses/#{course.id}/exercises/#{mod.id}"
+ when "poll"
+ "/courses/#{course.id}/polls/#{mod.id}"
+ when "attachment"
+ "/courses/#{course.id}/files/#{mod.id}"
+ when "board"
+ course_board = course.course_board
+ "/courses/#{course.id}/boards/#{course_board.id}"
+ when "course_group"
+ "/courses/#{course.id}/students"
+ end
+ end
+
+ # 子目录对应的url
+ def category_url category, course
+ case category.category_type
+ when "shixun_homework"
+ "/courses/#{course.id}/shixun_homework/#{category.id}"
+ when "graduation"
+ if category.name == "毕设选题"
+ "/courses/#{course.id}/graduation_topics/#{category.course_module_id}"
+ else
+ "/courses/#{course.id}/graduation_tasks/#{category.course_module_id}"
+ end
+ when "attachment"
+ "/courses/#{course.id}/file/#{category.id}"
+ end
+ end
+
+ # 子目录下的任务数
+ def category_task_count course, category, user
+ case category.category_type
+ when "shixun_homework"
+ get_homework_commons_count(course, 4, category.id)
+ when "graduation"
+ if category.name == "毕设选题"
+ course.graduation_topics_count
+ else
+ course.graduation_tasks_count
+ end
+ when "attachment"
+ get_attachment_count(course, category.id)
+ end
+ end
+
+ # 课堂模块的任务数
+ def course_task_count(course, module_type)
+ case module_type
+ when "shixun_homework"
+ get_homework_commons_count(course, 4, 0)
+ when "common_homework"
+ get_homework_commons_count(course, 1, 0)
+ when "group_homework"
+ get_homework_commons_count(course, 3, 0)
+ when "graduation"
+ 0
+ when "exercise"
+ course.exercises_count
+ when "poll"
+ course.polls_count
+ when "attachment"
+ get_attachment_count(course, 0)
+ when "board"
+ course_board = course.course_board
+ course_board.present? ? course_board.messages.size : 0
+ when "course_group"
+ course.course_groups_count
+ end
+ end
+
+ # 当前用户可见的课堂作业,type指定作业类型, category_id指定二级目录
+ def visible_homework course, user, type, category_id=0
+ if user.teacher_of_course?(course)
+ homeworks = course.homework_commons.where("homework_type = #{type} and course_second_category_id = #{category_id}")
+ elsif user.member_of_course?(course)
+ member = course.course_members.find_by(user_id: user.id, role: 4)
+ if member.try(:course_group_id).to_i == 0
+ homeworks = course.homework_commons.where("homework_commons.homework_type = #{type} and publish_time <= '#{Time.now}'
+ and unified_setting = 1 and course_second_category_id = #{category_id}")
+ else
+ not_homework_ids = course.homework_group_settings.where("course_group_id = #{member.try(:course_group_id)} and
+ (publish_time > '#{Time.now}' or publish_time is null)").pluck(:homework_common_id)
+ # not_homework_ids = not_homework_ids.blank? ? "(-1)" : "(" + not_homework_ids.map(&:homework_common_id).join(",") + ")"
+ homeworks = course.homework_commons.where.not(id: not_homework_ids).where("homework_commons.homework_type = #{type} and publish_time <= '#{Time.now}'
+ and course_second_category_id = #{category_id}")
+ end
+ else
+ homeworks = course.homework_commons.where("homework_type = #{type} and publish_time <= '#{Time.now}' and unified_setting = 1
+ and course_second_category_id = #{category_id}")
+ end
+ homeworks
+ end
+
+ # 当前用户可见的课堂试卷
+ def visible_exercise course, user
+ if user.teacher_of_course?(course)
+ exercises = course.exercises
+ elsif user.member_of_course?(course)
+ member = course.course_members.find_by(user_id: user.id, role: 4)
+ if member.try(:course_group_id).to_i == 0
+ exercises = course.exercises.where("publish_time <= '#{Time.now}' and unified_setting = 1")
+ else
+ not_exercise_ids = course.exercise_group_settings.where("course_group_id = #{member.try(:course_group_id)} and
+ (publish_time > '#{Time.now}' or publish_time is null)").pluck(:exercise_id)
+ exercises = course.exercises.where.not(id: not_exercise_ids).where("publish_time <= '#{Time.now}'")
+ end
+ else
+ exercises = course.exercises.where("publish_time <= '#{Time.now}' and unified_setting = 1")
+ end
+ exercises
+ end
+
+ # 当前用户可见的课堂问卷
+ def visible_poll course, user
+ if user.teacher_of_course?(course)
+ polls = course.polls
+ elsif user.member_of_course?(course)
+ member = course.course_members.find_by(user_id: user.id, role: 4)
+ if member.try(:course_group_id).to_i == 0
+ polls = course.polls.where("publish_time <= '#{Time.now}' and unified_setting = 1")
+ else
+ not_poll_ids = course.poll_group_settings.where("course_group_id = #{member.try(:course_group_id)} and
+ (publish_time > '#{Time.now}' or publish_time is null)").pluck(:poll_id)
+ polls = course.polls.where.not(id: not_poll_ids).where("publish_time <= '#{Time.now}'")
+ end
+ else
+ polls = course.polls.where("publish_time <= '#{Time.now}' and unified_setting = 1")
+ end
+ polls
+ end
+
+ # 当前用户可见的课堂资源,category_id指定资源的目录
+ def visible_attachment course, user, category_id=0
+ result = []
+ course.attachments.where(course_second_category_id: category_id).each do |attachment|
+ if attachment.unified_setting
+ if attachment.is_public == 1 && attachment.is_publish == 1 || user == attachment.author || user.teacher_of_course?(course) || (user.member_of_course?(course) && attachment.is_publish == 1)
+ result << attachment
+ end
+ else
+ if attachment.is_public == 1 && attachment.is_publish == 1 && !user.member_of_course?(course) || user == attachment.author || user.teacher_of_course?(course)
+ result << attachment
+ elsif user.member_of_course?(course) && attachment.is_publish == 1
+ member = course.course_members.find_by(user_id: user.id, role: 4)
+ if member.try(:course_group_id).to_i == 0 && attachment.unified_setting
+ result << attachment
+ elsif attachment.attachment_group_settings.where("course_group_id = #{member.try(:course_group_id)} and publish_time > '#{Time.now}'").count == 0
+ result << attachment
+ end
+ end
+ end
+ end
+ result
+ end
+
+ # 获取课堂的资源数
+ def get_attachment_count(course, category_id)
+ course.attachments.where(course_second_category_id: category_id).size
+ end
+
+ # 获取课堂的作业数
+ def get_homework_commons_count(course, type, category_id)
+ HomeworkCommon.where(course_id: course.id, homework_type: type, course_second_category_id: category_id).size
+ end
+
+
+ # 获取课堂的任务数(作业数+试卷数+问卷数)
+ def get_tasks_count(course)
+ course.homework_commons_count + course.exercises_count + course.polls_count
+ end
+
+ # 当前用户可见的毕设任务
+ def visible_graduation_task course, user
+ if user.teacher_of_course?(course)
+ tasks = course.graduation_tasks
+ else
+ tasks = course.graduation_tasks.where("publish_time <= '#{Time.now}'")
+ end
+ tasks
+ end
+
+ # 分班情况
+ def course_group_info course, user_id
+ course_group_ids = course.group_course_power(user_id)
+ course_groups =
+ if course_group_ids.present?
+ sql = %Q{
+ SELECT * FROM course_groups WHERE id in(SELECT course_group_id FROM teacher_course_groups WHERE id
+ IN(#{course_group_ids.join(",")})) order by position ASC
+ }
+ CourseGroup.find_by_sql(sql)
+ else
+ course.course_groups.includes(:course_members)
+ end
+ group_info = []
+ if course_groups.count > 0
+ course_groups.each do |group|
+ group_info << {course_group_id: group.id, group_group_name: group.name, count: group.course_members_count}
+ end
+
+ none_group_count = course.students.where(course_group_id: 0).size
+ group_info << {course_group_id: 0, group_group_name: "未分班", count: none_group_count} if none_group_count > 0 && !course_group_ids.present?
+ end
+
+ return group_info
+ end
+
+ def left_group_info course
+ group_info = []
+ course.course_groups.each do |course_group|
+ group_info << {category_id: course_group.id, category_name: course_group.name, position: course_group.position,
+ category_count: course_group.course_members_count, category_type: false,
+ second_category_url: "/courses/#{@course.id}/course_groups/#{course_group.id}"}
+ end
+ none_group_count = course.students.where(course_group_id: 0).size
+ group_info << {category_id: 0, category_name: "未分班", position: course.course_groups.pluck(:position).max.to_i + 1,
+ category_count: none_group_count, category_type: false,
+ second_category_url: "/courses/#{@course.id}/course_groups/0"}
+ end
+
+end
diff --git a/app/helpers/discusses_helper.rb b/app/helpers/discusses_helper.rb
new file mode 100644
index 000000000..c686fcada
--- /dev/null
+++ b/app/helpers/discusses_helper.rb
@@ -0,0 +1,2 @@
+module DiscussesHelper
+end
diff --git a/app/helpers/ecs_helper.rb b/app/helpers/ecs_helper.rb
new file mode 100644
index 000000000..5820cc0a7
--- /dev/null
+++ b/app/helpers/ecs_helper.rb
@@ -0,0 +1,2 @@
+module EcsHelper
+end
diff --git a/app/helpers/edu_settings_helper.rb b/app/helpers/edu_settings_helper.rb
new file mode 100644
index 000000000..831fb8c8a
--- /dev/null
+++ b/app/helpers/edu_settings_helper.rb
@@ -0,0 +1,2 @@
+module EduSettingsHelper
+end
diff --git a/app/helpers/exercise_questions_helper.rb b/app/helpers/exercise_questions_helper.rb
new file mode 100644
index 000000000..28f3fddb0
--- /dev/null
+++ b/app/helpers/exercise_questions_helper.rb
@@ -0,0 +1,48 @@
+module ExerciseQuestionsHelper
+ def get_exercise_question_info(question,exercise,ex_user,ex_answerer_id)
+ answered_content = []
+ exercise_answers = question.exercise_answers.search_exercise_answer("user_id",ex_answerer_id) #试卷用户的回答
+ if question.question_type <= 2
+ answered_content = exercise_answers.pluck(:exercise_choice_id)
+ elsif question.question_type == 3
+ exercise_answers.each do |a|
+ u_answer = {
+ "choice_id": a.exercise_choice_id,
+ "answer_text": a.answer_text
+ }
+ answered_content.push(u_answer)
+ end
+ elsif question.question_type == 4
+ answered_content = exercise_answers.pluck(:answer_text)
+ end
+ if question.question_type == 5 && ex_user.present? && ex_user.commit_status == 1 #存在实训题,且用户已提交了的,如果实训题只做了一半就关闭,则相当于不要了
+ if exercise.exercise_status == 3 #如果试卷已截止,则可以看到分数,否则不能查看分数
+ shixun_type = 2
+ else
+ shixun_type =1
+ end
+ else
+ shixun_type = 0
+ end
+ {
+ "answered_content":answered_content,
+ "shixun_type":shixun_type
+ }
+ end
+
+ def shixun_game_scores(challenge,ex_answerer,shixun_type,question_id)
+ game_score = challenge.question_score
+ game_answers = challenge.exercise_shixun_answers.search_shixun_answers("user_id",ex_answerer.id).search_shixun_answers("exercise_question_id",question_id)
+ if shixun_type == 2 && game_answers.present? #试卷已截止,用户才可以查看答案
+ s_score = game_answers.first.score
+ else
+ s_score = nil
+ end
+ games = ex_answerer.games.ch_games(challenge.challenge_id).uniq
+ {
+ "games":games,
+ "s_score":s_score,
+ "game_score":game_score
+ }
+ end
+end
diff --git a/app/helpers/exercises_helper.rb b/app/helpers/exercises_helper.rb
new file mode 100644
index 000000000..4f4c015eb
--- /dev/null
+++ b/app/helpers/exercises_helper.rb
@@ -0,0 +1,669 @@
+
+module ExercisesHelper
+
+ #获取每个学生对每个题的答案状态
+ def get_each_student_exercise(exercise_id,exercise_questions,user_id)
+ @exercise_user = ExerciseUser.current_exercise_user(user_id,exercise_id).first
+ exercise_obj_status = exercise_questions.find_objective_questions
+ @ex_obj_array = []
+ exercise_obj_status.each do |q|
+ if q.question_type == 5
+ ques_score = q.exercise_shixun_answers.search_shixun_answers("user_id",user_id).pluck(:score).sum
+ else
+ ques_score = q.exercise_answers.search_answer_users("user_id",user_id).score_reviewed.pluck(:score).sum
+ end
+
+ if ques_score == q.question_score #满分作答为正确
+ stand_answer = 1
+ elsif ques_score > 0.0 #部分作答
+ stand_answer = 2
+ else
+ stand_answer = 0
+ end
+
+ ques_option = {
+ "q_id":q.id, #该问题的id
+ "q_type":q.question_type,
+ "q_position":q.question_number, #该问题的位置
+ "stand_status":stand_answer, #用户是否回答
+ "user_score":ques_score.round(1).to_s #每个问题的总得分
+ }
+ @ex_obj_array.push(ques_option)
+ end
+ exercise_sub_status = exercise_questions.find_by_custom("question_type",4) #主观题
+ @ex_sub_array = [] #主观题的已答/未答
+ exercise_sub_status.each do |s|
+ sub_answer = s.exercise_answers.search_answer_users("user_id",user_id) #主观题只有一个回答
+ if sub_answer.present? && sub_answer.first.score >= 0.0
+ if s.question_score == sub_answer.first.score
+ stand_status = 1
+ else
+ stand_status = 2
+ end
+ # stand_status = 1
+ sub_answer_score = sub_answer.first.score
+ else
+ stand_status = 0
+ sub_answer_score = 0.0
+ end
+
+ sub_score = {
+ "q_id":s.id,
+ "q_type":s.question_type,
+ "q_position":s.question_number,
+ "stand_status":stand_status,
+ "user_score":sub_answer_score.round(1).to_s
+ }
+ @ex_sub_array.push(sub_score)
+ end
+ @ex_obj_array.sort_by {|k| k[:q_position]}
+ @ex_sub_array.sort_by {|k| k[:q_position]}
+ end
+
+ #试卷的统计结果页面计算各题的
+ def exercise_commit_result(questions,user_ids)
+ question_infos = []
+ questions.each do |ex|
+ ex_total_score = user_ids.count * ex&.question_score #该试卷的已回答的总分
+ ex_answers = ex.exercise_answers
+ if ex.question_type != 5
+ ques_title = ex.question_title
+ ques_less_title = nil
+ effictive_users = ex_answers.search_answer_users("user_id",user_ids)
+ else
+ ques_title = ex.shixun.name
+ ques_less_title = ex.question_title
+ effictive_users = ex.exercise_shixun_answers.search_shixun_answers("user_id",user_ids)
+ end
+ effictive_users_count = effictive_users.count #有效回答数,可能有重复的用户id,这里仅统计是否回答这个问题的全部人数
+ ex_answered_scores = effictive_users.score_reviewed.pluck(:score).sum #该问题的全部得分
+ if ex_total_score == 0.0
+ percent = 0.0
+ else
+ percent = (ex_answered_scores / ex_total_score.to_f).round(3) * 100 #正确率
+ end
+ question_answer_infos = []
+ if ex.question_type <= 2 #单选题
+ standard_answer = ex.exercise_standard_answers.pluck(:exercise_choice_id) #标准答案的位置
+ ex.exercise_choices.each do |c|
+ if standard_answer.include?(c.choice_position) #选项的标准答案为选项的位置
+ right_answer = true
+ else
+ right_answer = false
+ end
+ answer_this_choice = effictive_users.search_exercise_answer("exercise_choice_id",c.id)
+ answer_users_count = answer_this_choice.count
+ if effictive_users_count == 0
+ answer_percent = 0.0
+ else
+ answer_percent = (answer_users_count / effictive_users_count.to_f ).round(3)
+ end
+ answer_option = {
+ :choice_position => c.choice_position,
+ :choice_text => c.choice_text,
+ :choice_users_count => answer_users_count,
+ :choice_percent => answer_percent.round(1),
+ :right_answer => right_answer
+ }
+ question_answer_infos.push(answer_option)
+ end
+ elsif ex.question_type == 3 #填空题
+ ex_ordered = ex.is_ordered
+ null_standard_answer = ex.exercise_standard_answers
+ null_stand_choice = null_standard_answer.pluck(:exercise_choice_id)
+ null_stand_text = null_standard_answer.pluck(:answer_text)
+ standard_answer_count = 0
+ all_user_count = 0
+ null_stand_choice.each_with_index do |s,index|
+ user_count = 0
+ s_choice_text = null_stand_text[index]
+ if ex_ordered #有序排列
+ user_ids.each do |u|
+ user_answers = ex_answers.search_answer_users("user_id",u).search_answer_users("exercise_choice_id",s)
+ user_answers_choice = user_answers.present? ? user_answers.first.answer_text : ""
+ if s_choice_text == user_answers_choice
+ user_count += 1
+ end
+ end
+ else
+ user_count = user_count + effictive_users.search_exercise_answer("answer_text",s_choice_text).count #回答了标准答案的用户
+ end
+ if effictive_users_count == 0
+ answer_percent = 0.0
+ else
+ answer_percent = (user_count / effictive_users_count.to_f ).round(3)
+ end
+
+ answer_option = {
+ :choice_position => index+1,
+ :choice_text => s_choice_text,
+ :choice_users_count => user_count,
+ :choice_percent => answer_percent.round(1),
+ :right_answer => true
+ }
+ question_answer_infos.push(answer_option)
+ all_user_count += user_count
+ standard_answer_count += 1
+ end
+ user_wrong_count = (effictive_users_count - all_user_count )
+
+ if effictive_users_count > 0 && user_wrong_count >= 0
+ wrong_percent = (user_wrong_count / effictive_users_count.to_f ).round(3)
+ else
+ wrong_percent = 0.0
+ end
+ wrong_answer_position = {
+ :choice_position => (standard_answer_count + 1),
+ :choice_text => "wrong",
+ :choice_users_count => user_wrong_count,
+ :choice_percent => wrong_percent.round(1),
+ :right_answer => false
+ }
+ question_answer_infos.push(wrong_answer_position)
+ elsif ex.question_type == 4 #主观题
+ ex_score = ex&.question_score
+ full_scores = effictive_users.search_exercise_answer("score",ex_score).count #满分人数
+ no_full_scores = effictive_users.exercise_no_full_scores(ex_score).count #部分分数人数
+ all_zero_scores = effictive_users.search_exercise_answer("score",0.0).count #包含为0分的,及未评阅的
+ review_scores = ex.exercise_answer_comments.count #主观题的评阅数量
+ un_review_scores = effictive_users_count - review_scores #未评阅数
+ zero_scores = all_zero_scores - un_review_scores #已评阅,且答案未0分的人数
+ main_scores_array = [full_scores,no_full_scores,zero_scores,un_review_scores]
+ main_scores_array.each_with_index do |s,index|
+ if index == 0
+ right_answer = true
+ else
+ right_answer = false
+ end
+ if effictive_users_count == 0 || s < 0
+ s = 0
+ score_percent = 0.0
+ else
+ score_percent = (s.to_i / effictive_users_count.to_f ).round(3)
+ end
+ answer_option = {
+ :choice_position => index+1,
+ :choice_text => index+1,
+ :choice_users_count => s,
+ :choice_percent => score_percent.round(1),
+ :right_answer => right_answer
+ }
+ question_answer_infos.push(answer_option)
+ end
+ elsif ex.question_type == 5 #实训题
+ ex.exercise_shixun_challenges.each do |c|
+ cha_score = c&.question_score
+ cha_shixun_answer = effictive_users.search_shixun_keys("exercise_shixun_challenge_id",c.id)
+ effictive_users_count = cha_shixun_answer.count #实训题的每个关卡的有效填写量
+ full_scores = cha_shixun_answer.search_shixun_keys("score",cha_score).count #满分人数
+ no_full_scores = cha_shixun_answer.shixun_no_full_scores(cha_score).count #部分分数人数c
+ all_zero_scores = cha_shixun_answer.search_shixun_keys("score",0.0).count #满分人数
+ shixun_score_array = [full_scores,no_full_scores,all_zero_scores]
+ shixun_chas = []
+ shixun_score_array.each_with_index do |s,index|
+ if index == 0
+ right_answer = true
+ else
+ right_answer = false
+ end
+ if effictive_users_count == 0
+ score_percent = 0.0
+ else
+ score_percent = (s.to_i / effictive_users_count.to_f ).round(3)
+ end
+ answer_option = {
+ :choice_position => index+1,
+ :choice_text => index+1,
+ :choice_users_count => s,
+ :choice_percent => score_percent.round(1),
+ :right_answer => right_answer
+ }
+ shixun_chas.push(answer_option)
+ end
+ shixun_new_chas = {
+ :cha_id => c.challenge_id,
+ :cha_name => c.challenge.subject,
+ :cha_position => c.position,
+ :cha_details => shixun_chas
+ }
+ question_answer_infos.push(shixun_new_chas)
+ end
+ end
+ ques_option = {
+ :ques_title => ques_title,
+ :ques_less_title => ques_less_title, #副标题,仅实训题才有
+ :type => ex.question_type,
+ :position => ex.question_number,
+ :percent => percent.round(1).to_s,
+ :ques_effictive_counts => effictive_users_count,
+ :ques_details => question_answer_infos
+ }
+ question_infos.push(ques_option)
+ end
+ question_infos
+ end
+
+ #获取试卷的已答/未答人数
+ def get_exercise_answers(ex_users)
+ @commit_ex_users = ex_users.commit_exercise_by_status(1) #当前老师的全部学生中已提交的
+
+ @exercise_answers = @commit_ex_users.present? ? @commit_ex_users.size : 0 #表示已经提交了的用户
+ course_all_members_count = ex_users.present? ? ex_users.size : 0
+ @exercise_unanswers = (course_all_members_count - @exercise_answers)
+ end
+
+ def exercise_index_show(exercise,course,is_teacher_or,user)
+ # exercise_all_users = exercise.exercise_users
+ ex_show_text = []
+
+ if course.is_end #课堂停止后,试卷显示为已结束
+ exercise_status = 4
+ elsif is_teacher_or == 1 #当前为老师的时候,显示的是老师身份的对应试卷的状态,因为该试卷,可能对应老师的多个分班
+ exercise_status = exercise.exercise_status
+ else
+ exercise_status = exercise.get_exercise_status(user.id) #当前用户查看的试卷的发布状态
+ end
+
+ case exercise_status
+ when 2
+ ex_show_text.push("提交中")
+ when 3
+ ex_show_text.push("已截止")
+ when 4
+ ex_show_text.push("已结束")
+ else
+ ex_show_text
+ end
+ if is_teacher_or == 1
+ exercise_users_list = exercise.all_exercise_users(user.id) #当前老师所在班级的全部学生
+ unreview_count = exercise_users_list.exercise_unreview.size
+ get_exercise_answers(exercise_users_list)
+ ex_pb_time = exercise.get_exercise_times(user.id,true)
+ exercise_publish_time = ex_pb_time[:publish_time]
+ exercise_end_time = ex_pb_time[:end_time]
+ current_status = 3
+ lock_icon = 1 #不显示锁图标
+ if exercise_status == 1
+ ex_show_text.push("未发布")
+ elsif exercise_status == 3
+ ex_show_text.push("评阅中")
+ end
+ elsif is_teacher_or == 2
+ exercise_users_list = exercise.get_stu_exercise_users
+ get_exercise_answers(exercise_users_list) # 未答和已答的
+ unreview_count = exercise_users_list.exercise_unreview.size
+ ex_pb_time = exercise.get_exercise_times(user.id,false)
+ exercise_publish_time = ex_pb_time[:publish_time]
+ exercise_end_time = ex_pb_time[:end_time]
+ current_status = exercise.check_user_answer_status(user)
+ lock_icon = 1 #不显示锁图标
+ if current_status == 4
+ ex_show_text.push("未提交")
+ end
+ else
+ exercise_users_list = exercise.get_stu_exercise_users
+ get_exercise_answers(exercise_users_list) # 未答和已答的
+ exercise_publish_time = exercise.publish_time
+ exercise_end_time = exercise.end_time
+ unreview_count = nil
+ if exercise.is_public
+ current_status = exercise.check_user_answer_status(user)
+ lock_icon = 1 #非课堂成员,但是试卷为公开的,不加锁
+ if current_status == 4
+ ex_show_text.push("未提交")
+ end
+ else
+ current_status = 4
+ lock_icon = 0 #显示锁图标
+ end
+ end
+
+ if exercise_status > 1
+ show_unreview_count = unreview_count
+ else
+ show_unreview_count = nil
+ end
+
+ if exercise_status == 2 && exercise_end_time.present?
+ ex_left_time = how_much_time(exercise_end_time)
+ elsif exercise_status == 3 && course.end_date.present?
+ ex_left_time = how_much_time(course.end_date.to_time)
+ else
+ ex_left_time = nil
+ end
+
+ {
+ "publish_time":exercise_publish_time,
+ "end_time":exercise_end_time,
+ "exercise_answer":@exercise_answers,
+ "exercise_unanswer":@exercise_unanswers,
+ "current_status":current_status,
+ "lock_icon":lock_icon,
+ "unreview_count": show_unreview_count,
+ "ex_status":exercise_status,
+ "ex_tips":ex_show_text,
+ "ex_left_time": ex_left_time
+ }
+ end
+
+ #计算试卷的总分和试卷的答题状态
+ def calculate_student_score(exercise,user)
+ score1 = 0.0 #选择题/判断题
+ score2 = 0.0 #填空题
+ score5 = 0.0 #实训题
+ ques_stand = [] #问题是否正确
+ exercise_questions = exercise.exercise_questions
+ exercise_questions.each do |q|
+ if q.question_type != 5
+ answers_content = q.exercise_answers.search_answer_users("user_id",user.id) #学生的答案
+ else
+ answers_content = q.exercise_shixun_answers.search_shixun_answers("user_id",user.id) #学生的答案
+ end
+ if q.question_type <= 2 #为选择题或判断题时
+ answer_choice_array = []
+ answers_content.each do |a|
+ answer_choice_array.push(a.exercise_choice.choice_position) #学生答案的位置
+ end
+ user_answer_content = answer_choice_array.sort
+ standard_answer = q.exercise_standard_answers.pluck(:exercise_choice_id).sort #该问题的标准答案,可能有多个
+ if user_answer_content == standard_answer #答案一致,多选或单选才给分,答案不对不给分
+ if standard_answer.count > 0
+ multi_each_score = (q.question_score / standard_answer.count) #当多选答案正确时,每个answer的分数均摊。
+ else
+ multi_each_score = 0.0
+ end
+ answers_content.update_all(:score => multi_each_score)
+ score1 = score1 + q.question_score
+ end
+ elsif q.question_type == 3 #填空题
+ null_standard_answer = q.exercise_standard_answers
+ standard_answer_array = null_standard_answer.select(:exercise_choice_id,:answer_text)
+ standard_answer_ids = standard_answer_array.pluck(:exercise_choice_id).reject(&:blank?).uniq #标准答案的exercise_choice_id数组
+ standard_answer_count = standard_answer_ids.count
+ if standard_answer_count > 0 #存在标准答案时才有分数
+ each_standard_score = (q.question_score.to_f / standard_answer_count).round(1) #每一空的得分
+ else
+ each_standard_score = 0.0
+ end
+ if q.is_ordered
+ answers_content.each do |u|
+ i_standard_answer = standard_answer_array.where(exercise_choice_id:u.exercise_choice_id).pluck(:answer_text).reject(&:blank?).map!(&:downcase) #该选项的全部标准答案
+ if i_standard_answer.include?(u.answer_text.downcase) #该空的标准答案包含用户的答案才有分数
+ u.update_attribute("score",each_standard_score)
+ score2 = score2 + each_standard_score
+ end
+ end
+ else
+ st_answer_text = standard_answer_array.pluck(:answer_text).reject(&:blank?).map!(&:downcase)
+ answers_content.each do |u|
+ u_answer_text = u.answer_text.downcase
+ if st_answer_text.include?(u_answer_text) #只要标准答案包含用户的答案,就有分数。同时,下一次循环时,就会删除该标准答案。防止用户的相同答案获分
+ u.update_attribute("score",each_standard_score)
+ score2 = score2 + each_standard_score
+ st_answer_text.delete(u_answer_text)
+ end
+ end
+ end
+ elsif q.question_type == 5 #实训题时,主观题这里不评分
+ q.exercise_shixun_challenges.each do |exercise_cha|
+ game = Game.user_games(user.id,exercise_cha.challenge_id).first #当前用户的关卡
+ if game.present?
+ exercise_cha_score = 0
+ answer_status = 0
+ cha_path = challenge_path exercise_cha.challenge.path
+ if game.status == 2 && game.final_score >= 0
+ exercise_cha_score = game.real_score exercise_cha.question_score #每一关卡的得分
+ answer_status = 1
+ end
+ if exercise_cha.exercise_shixun_answers.search_shixun_answers("user_id",user.id).blank? #把关卡的答案存入试卷的实训里
+ game_challenge = game.game_codes.search_challenge_path(cha_path).first
+ if game_challenge.present?
+ game_code = game_challenge
+ code = game_code.try(:new_code)
+ else
+ code = git_fle_content(exercise_cha.shixun.repo_path,cha_path)
+ end
+ sx_option = {
+ :exercise_question_id => q.id,
+ :exercise_shixun_challenge_id => exercise_cha.id,
+ :user_id => user.id,
+ :score => exercise_cha_score,
+ :answer_text => code,
+ :status => answer_status
+ }
+ ExerciseShixunAnswer.create(sx_option)
+ end
+ score5 += exercise_cha_score
+ end
+ end
+ end
+ user_scores = answers_content.score_reviewed.pluck(:score).sum
+ if user_scores > 0
+ stand_answer = 1
+ else
+ stand_answer = 0
+ end
+ ques_option = {
+ "q_id":q.id, #该问题的id
+ "q_type":q.question_type,
+ "q_position":q.question_number, #该问题的位置
+ "stand_status":stand_answer, #该问题是否正确,1为正确,0为错误
+ "user_score":user_scores #每个问题的总得分
+ }
+ ques_stand.push(ques_option)
+ end
+ total_score = score1 + score2 + score5
+ {
+ "total_score":total_score,
+ "stand_status":ques_stand
+ }
+ end
+
+ #获取用户的相关信息
+ def exercise_use_info(ex_user,user_status,exercise)
+ course = exercise.course
+ current_user_group_id = ""
+ current_user_group_name = ""
+ ex_user_user = ex_user.user
+ exercise_user_name = ex_user_user.real_name
+ exercise_user_id = ex_user_user.id
+ ex_user_exercise_status = exercise.get_exercise_status(exercise_user_id)
+ ex_user_student_id = ex_user_user.student_id
+ if ex_user.start_at.present? && ex_user.commit_status == 0 #用户已回答,但未提交
+ commit_status = 2 #继续答题
+ else
+ commit_status = ex_user.commit_status
+ end
+
+ ex_user_end_at = ex_user.end_at
+ course_member = course.students.course_find_by_ids("user_id",ex_user.user.id)
+ current_user_group_id = course_member.first.course_group_id if course_member.present?
+ course_group = course.course_groups.by_group_ids(current_user_group_id)
+ current_user_group_name = course_group.first.name if course_group.present?
+ teacher_review = ex_user.subjective_score < 0.0 ? false : true
+ if ex_user_exercise_status != 3 || commit_status != 1 #试卷未截止或用户未提交
+ # if (user_status != 0 && ex_user_exercise_status != 3)|| commit_status == 0 #不为教师,且试卷未截止;当前用户未提交 不显示分数
+ ex_object_score = nil
+ ex_subject_score = nil
+ score = nil
+ else
+ ex_object_score = ex_user.objective_score < 0.0 ? 0.0 : ex_user.objective_score.round(1).to_s
+ ex_subject_score = ex_user.subjective_score < 0.0 ? nil : ex_user.subjective_score.round(1).to_s
+ score = ex_user.score.present? ? ex_user.score.round(1).to_s : 0.0.to_s
+ end
+
+ {
+ "user_name":exercise_user_name,
+ "login":ex_user_user.login,
+ "user_group_id":current_user_group_id,
+ "user_group_name":current_user_group_name,
+ "teacher_review":teacher_review,
+ "ex_object_score":ex_object_score,
+ "ex_subject_score":ex_subject_score,
+ "score":score,
+ "user_id":exercise_user_id,
+ "commit_status":commit_status,
+ "student_id":ex_user_student_id,
+ "end_at":ex_user_end_at
+ }
+ end
+
+ #公用tab页的相关信息
+ def ex_common_header(is_teacher_or,exercise)
+ exercise_url_status = []
+ if is_teacher_or == 1 #当为老师的
+ common_tabs = %w(1 2 3 4)
+ else
+ if exercise.show_statistic #开启了公开统计
+ common_tabs = %w(1 2)
+ else
+ common_tabs = %w(1)
+ end
+ end
+
+ common_tabs.each do |c|
+ if request.url.include?("exercise_lists")
+ active_status = 1
+ elsif request.url.include?("exercises_result")
+ active_status = 1
+ elsif request.url.include?("exercise_setting")
+ active_status = 1
+ elsif request.url == exercise_path(exercise)
+ active_status = 1
+ else
+ active_status = 0
+ end
+ common_tab = {
+ :common_tab => c,
+ :active_status => active_status
+ }
+ exercise_url_status.push(common_tab)
+ end
+ exercise_url_status
+ end
+
+ #试卷/问卷 设置页面的发布规则返回内容
+ def get_user_setting_course(user_published_setting,user_course_groups)
+ loop_course_publish_time = []
+ total_array_groups = []
+ user_published_setting.each do |u|
+ new_json_array_group = {}
+ setting_group_name = user_course_groups.detect{|g| g[:group_id] == u.course_group_id}
+ if loop_course_publish_time.length > 0
+ time_length = loop_course_publish_time.length #问卷发布时间和截止时间相同的集合
+ (1..time_length).each do |i|
+ if (loop_course_publish_time[i-1][:publish_time] == u.publish_time) && (loop_course_publish_time[i-1][:end_time] == u.end_time) #当起止时间已存在时,直接更新
+ loop_course_ids = total_array_groups[i-1][:course_group_id] +[u.course_group_id]
+ loop_course_names = total_array_groups[i-1][:course_group_name] + [setting_group_name[:group_name]]
+ new_json_array_group = {
+ "loop_times":i-1,
+ "course_group_id":loop_course_ids,
+ "course_group_name":loop_course_names,
+ }
+ end
+ end
+ if new_json_array_group.length > 0
+ loop_times = new_json_array_group[:loop_times]
+ total_array_groups[loop_times][:course_group_id] = new_json_array_group[:course_group_id]
+ total_array_groups[loop_times][:course_group_name] = new_json_array_group[:course_group_name]
+ else
+ loop_course_times = {
+ "publish_time":u.publish_time,
+ "end_time": u.end_time
+ }
+ loop_course_publish_time.push(loop_course_times)
+ json_array_group = {
+ "course_group_id":[u.course_group_id],
+ "course_group_name":[setting_group_name[:group_name]],
+ "course_publish_time":u.publish_time,
+ "course_end_time":u.end_time
+ }
+ total_array_groups.push(json_array_group)
+ end
+ else #第一次循环获得初始值 第一步
+ loop_course_times = {
+ "publish_time":u.publish_time,
+ "end_time": u.end_time
+ }
+ loop_course_publish_time.push(loop_course_times)
+ json_array_group = { #第一个问卷发布规则的第一条记录
+ "course_group_id":[u.course_group_id],
+ "course_group_name":[setting_group_name[:group_name]],
+ "course_publish_time":u.publish_time,
+ "course_end_time":u.end_time
+ }
+ total_array_groups.push(json_array_group)
+ end
+ end
+ total_array_groups
+ end
+
+ #学生的分数状态及回答的内容
+ def user_question_answers(q,ex_answerer_id,student_status,is_teacher_or,ex_status,ques_type,ex_type)
+ answered_content = []
+ user_score = 0.0
+ shixun_type = 0
+ question_comment = []
+ if q.question_type == 5
+ exercise_answers = q.exercise_shixun_answers.search_shixun_answers("user_id",ex_answerer_id)
+ else
+ exercise_answers = q.exercise_answers.search_exercise_answer("user_id",ex_answerer_id) #试卷用户的回答
+ end
+ if student_status == 2 #当前为老师,或为学生且已提交
+ user_score = exercise_answers.score_reviewed.pluck(:score).sum
+ end
+ if ques_type <= 2
+ answered_content = exercise_answers.pluck(:exercise_choice_id)
+ elsif ques_type == 3
+ exercise_answers.each do |a|
+ u_answer = {
+ "choice_id":a.exercise_choice_id,
+ "answer_text": a.answer_text
+ }
+ answered_content.push(u_answer)
+ end
+ elsif ques_type == 4
+ answered_content = exercise_answers.pluck(:answer_text)
+ end
+ if ques_type == 5 #存在实训题,及已经做了实训题的
+ if ex_status == 3 || is_teacher_or == 1 #如果试卷已截止,则可以看到分数,否则不能查看分数
+ shixun_type = 2
+ elsif ex_status == 2 && q.exercise_shixun_answers.present? #试卷未截止,且用户已回答的,则能看到答题的状态
+ shixun_type =1
+ end
+ end
+
+ if ex_type == 4 #填空题/主观题/实训题有评论的
+ q_answer_id = exercise_answers.present? ? exercise_answers.first.id : nil
+ question_comment = q.exercise_answer_comments.search_answer_comments("exercise_answer_id",q_answer_id)
+ end
+ {
+ "user_score": user_score.round(1),
+ "answered_content":answered_content,
+ "shixun_type":shixun_type,
+ "question_comment":question_comment
+ }
+ end
+
+ def convert_to_char(str)
+ result = ""
+ length = str.length
+ unless str.nil?
+ if length === 1
+ result += (str.to_i + 64).chr
+ return result
+ elsif length > 1
+ for i in 0...length
+ result += (str[i].to_i + 64).chr
+ end
+ return result
+ end
+ end
+ result
+ end
+
+ #实训题学生代码的行数
+ def content_line(content)
+ content.split(/\r?\n/).length
+ end
+end
diff --git a/app/helpers/export_helper.rb b/app/helpers/export_helper.rb
new file mode 100644
index 000000000..a5e3c5d9d
--- /dev/null
+++ b/app/helpers/export_helper.rb
@@ -0,0 +1,488 @@
+module ExportHelper
+ include ApplicationHelper
+ require "base64"
+ require 'zip'
+
+ # 附件导出最大值
+ MAX_DOWN_SIZE = 500 * 1024 * 1024
+ SAVE_FOLDER = "#{Rails.root}/files"
+ OUTPUT_FOLDER = "#{Rails.root}/files/archiveZip"
+ MAX_PATH = 50
+
+ # 作业导出为xlsx,homework_type 1:普通作业 2:编程作业(弃用) 3:分组作业 4:实训作业
+ def student_work_to_xlsx(works,homework)
+ @work_head_cells = []
+ @work_cells_column = []
+ course = homework.course
+ head_cells_format = %w(序号 登录名 真实姓名 邮箱 学号 分班 提交状态)
+ allow_late_boolean = homework.allow_late
+ if allow_late_boolean #允许迟交
+ allow_late_cell = ["迟交扣分"]
+ else
+ allow_late_cell = []
+ end
+ if homework.homework_type != "practice" #普通作业/分组作业
+ homework_type_name = homework.homework_type
+ if homework_type_name == "group" #分组作业
+ group_cells = ["关联项目"]
+ else
+ group_cells = []
+ end
+ normal_head_cells = %w(作品描述 教师评分 教辅评分)
+ anon_boolean = homework.anonymous_comment
+ if anon_boolean
+ head_cells_add = %w(匿名评分 缺评扣分 违规匿评申诉扣分)
+ else
+ head_cells_add = []
+ end
+ normal_head_b_cells = %w(最终成绩 提交时间 更新时间)
+ @work_head_cells = (head_cells_format + group_cells + normal_head_cells + head_cells_add + allow_late_cell + normal_head_b_cells).reject(&:blank?)
+ works.each_with_index do |w, index|
+ w_user = w.user
+ w_1 = (index + 1)
+ w_2 = w_user.login
+ w_3 = w_user.real_name
+ w_3_1 = w_user.mail
+ w_4 = w_user.student_id.present? ? w_user.student_id : "--"
+ course_name = course.course_member(w.user_id).try(:course_group_name)
+ w_5 = course_name.present? ? course_name : "--"
+ #0: 未提交, 1 按时提交, 2 延迟提交
+ if w.work_status == 0
+ w_6 = "未提交"
+ elsif w.work_status == 1
+ w_6 = "按时提交"
+ elsif w.work_status == 2
+ w_6 = "延迟提交"
+ else
+ w_6 = "--"
+ end
+ if homework_type_name == "group"
+ project_name = w.project
+ if project_name.present?
+ w_7 = w.project.name
+ else
+ w_7 = "--"
+ end
+ else
+ w_7 = nil
+ end
+ w_8 = w.description.present? ? strip_html(w.description) : "--"
+ w_9 = w.teacher_score.nil? ? "未评分" : w.teacher_score.round(1)
+ w_10 = w.teaching_asistant_score.nil? ? "未评分" : w.teaching_asistant_score.round(1)
+ if anon_boolean
+ w_11 = w.student_score.nil? ? "未评分" : w.student_score.round(1)
+ w_12 = (homework.teacher_priority == 1 && !w.teacher_score.nil?) ? 0 : w.absence_penalty #缺评扣分
+ home_work_de = homework.homework_detail_manual
+ w_13 = home_work_de.present? ? home_work_de.appeal_penalty : "--" #违规匿评申诉扣分
+ else
+ w_11,w_12,w_13 = nil
+ end
+ if allow_late_boolean #允许迟交
+ w_14 = (homework.teacher_priority == 1 && !w.teacher_score.nil?) ? 0 : w.late_penalty #迟交扣分
+ else
+ w_14 = nil
+ end
+ w_15 = w.work_score.nil? ? "未评分" : w.work_score.round(1)
+ w_16 = w.commit_time ? format_time(w.commit_time) : "--"
+ w_17 = w.update_time ? format_time(w.update_time) : "--"
+
+ row_cells_column = [w_1,w_2,w_3,w_3_1,w_4,w_5,w_6,w_7,w_8,w_9,w_10,w_11,w_12,w_13,w_14,w_15,w_16,w_17]
+ row_cells_column = row_cells_column.reject(&:blank?)
+ @work_cells_column.push(row_cells_column)
+ end
+ else #实训题
+ shixun = homework.shixuns.first
+ shixun_head_cells = %w(完成情况 通关时间 总耗时 总评测次数 获得经验值 关卡得分)
+ eff_boolean = homework.work_efficiency
+ if eff_boolean
+ eff_score_cell = ["效率分"]
+ else
+ eff_score_cell = []
+ end
+ if allow_late_boolean #允许迟交
+ eff_score_cell.push("迟交扣分")
+ end
+ shixun_time_cells = %w(最终成绩 更新时间 提交耗时)
+ @work_head_cells = (head_cells_format + shixun_head_cells + eff_score_cell + shixun_time_cells).reject(&:blank?)
+ works.each_with_index do |w, index|
+ myshixun = w.try(:myshixun)
+ w_user = w.user
+ w_1 = (index + 1)
+ w_2 = w_user.login
+ w_3 = w_user.real_name
+ w_3_1 = w_user.mail
+ w_4 = w_user.student_id.present? ? w_user.student_id : "--"
+ course_name = course.course_member(w.user_id).try(:course_group_name)
+ w_5 = course_name.present? ? course_name : "--"
+ #0: 未提交, 1 按时提交, 2 延迟提交
+ if w.work_status == 0
+ w_6 = "未提交"
+ elsif w.work_status == 1
+ w_6 = "按时提交"
+ elsif w.work_status == 2
+ w_6 = "延迟提交"
+ else
+ w_6 = "--"
+ end
+ w_7 = w.work_status == 0 ? '--' : myshixun.try(:passed_count).to_s+"/"+shixun.challenges.count.to_s
+ w_8 = myshixun ? myshixun.try(:passed_time) == "--" ? "--" : format_time(myshixun.try(:passed_time)) : "--" # 通关时间
+ w_9 = myshixun ? (myshixun.try(:passed_count) > 0 ? myshixun.total_spend_time : '--') : "--" #总耗时
+ w_10 = myshixun ? myshixun.output_times : 0 #评测次数
+ w_11 = myshixun ? myshixun.total_score : "--" #获得经验值
+ w_12 = w.final_score.present? ? w.final_score : 0
+ if eff_boolean
+ w_13 = w.eff_score
+ else
+ w_13 = nil
+ end
+ if allow_late_boolean #允许迟交
+ w_14 = w.late_penalty #迟交扣分
+ else
+ w_14 = nil
+ end
+ w_15 = w.work_score.nil? ? "--" : w.work_score.round(1)
+ w_16 = w.update_time ? format_time(w.update_time) : "--" "更新时间"
+ w_17 = w.cost_time
+ row_cells_column = [w_1,w_2,w_3,w_3_1,w_4,w_5,w_6,w_7,w_8,w_9,w_10,w_11,w_12,w_13,w_14,w_15,w_16,w_17]
+ row_cells_column = row_cells_column.reject(&:blank?)
+ @work_cells_column.push(row_cells_column)
+ end
+ end
+ end
+
+ #毕设任务的导出
+ def graduation_work_to_xlsx(items,task,current_user)
+ head_cells_format = %w(序号 登录名 真实姓名 邮箱 学号 分班)
+ task_type_boolean = task.task_type == 2
+ if task_type_boolean #是否分组
+ head_cells_format = head_cells_format + ["分组"]
+ end
+ head_cells_format = head_cells_format + ["提交状态"]
+ task_project_boolean = task.base_on_project
+ if task_project_boolean #关联项目
+ head_cells_format = head_cells_format + ["关联项目"]
+ end
+
+ head_cells_format = head_cells_format + %w(作品描述 教师评分)
+
+ task_comment_boolean = task.cross_comment
+ if task_comment_boolean #是否交叉评阅
+ head_cells_format = head_cells_format + ["交叉评分"]
+ end
+
+ head_cells_format = head_cells_format + %w(迟交扣分 最终成绩 提交时间 更新时间)
+
+ @head_cells_column = head_cells_format
+ @task_cells_column = []
+
+ items.each_with_index do |work,index|
+ w_1 = (index+1)
+ w_user = work.user
+ w_2 = w_user.login
+ w_3 = w_user.real_name
+ w_3_1 = w_user.mail
+ w_4 = w_user.student_id.present? ? w_user.student_id : "--"
+ w_5 = work.class_grouping_name
+ if task_type_boolean #是否分组
+ w_6 = work.grouping_name
+ else
+ w_6 = nil
+ end
+ w_7 = work.work_status
+ if task_project_boolean #关联项目
+ w_project = project_info work, current_user, @user_course_identity #因为课堂引用了export_helper
+ w_8 = w_project[:name]
+ else
+ w_8 = nil
+ end
+
+ if work.description
+ w_9 = strip_html work.description
+ else
+ w_9 = "--"
+ end
+
+ w_10 = work.teacher_score.nil? ? "未评分" : work.teacher_score.round(1)
+ if task_comment_boolean #是否交叉评阅
+ w_11 = work.cross_score.nil? ? "未评分" : work.cross_score.round(1)
+ else
+ w_11 = nil
+ end
+ w_12 = work.late_penalty
+ w_13 = work.work_score.nil? ? "未评分" : work.work_score.round(1)
+ w_14 = work.commit_time.present? ? format_time(work.commit_time) : "--"
+ w_15 = work.update_time.present? ? format_time(work.update_time) : "--"
+
+ row_cells_column = [w_1,w_2,w_3,w_3_1,w_4,w_5,w_6,w_7,w_8,w_9,w_10,w_11,w_12,w_13,w_14,w_15]
+
+ row_cells_column = row_cells_column.reject(&:blank?)
+ @task_cells_column.push(row_cells_column)
+ end
+ end
+
+ #试卷的导出
+ def get_export_users(exercise,course,export_ex_users)
+ question_types = exercise.exercise_questions.pluck(:question_type).uniq
+ @table_columns = %w(序号 登录名 真实姓名 邮箱 学号 分班 提交状态)
+ @user_columns = []
+ ques_type_boolean = question_types.include?(4)
+ if ques_type_boolean #仅存在主观题或客观题的时候
+ @table_columns = @table_columns + %w(客观题成绩 主观题成绩 最终成绩 开始答题时间 提交时间)
+ else
+ @table_columns = @table_columns + %w(最终成绩 开始答题时间 提交时间)
+ end
+ export_ex_users.each_with_index do |e_user,index|
+ user_info = e_user.user
+ user_course_id = user_info.course_members.course_find_by_ids("course_id",course.id).first
+ if user_course_id.present?
+ get_course_group_id = user_course_id.course_group_id
+ if get_course_group_id.present? && get_course_group_id != 0
+ user_course_info = CourseGroup.by_group_ids(get_course_group_id)
+ user_course = user_course_info.first.name
+ else
+ user_course = "--"
+ end
+ else
+ user_course = "--"
+ end
+ user_obj_score = e_user.objective_score < 0.0 ? 0.0 : e_user.subjective_score.round(1).to_s
+ user_suj_score = e_user.subjective_score < 0.0 ? 0.0 : e_user.subjective_score.round(1).to_s
+ user_score = e_user.score.present? ? e_user.score.round(1).to_s : 0.0
+ if e_user.commit_status.present? && e_user.commit_status == 1
+ user_commit_stu = "按时提交"
+ else
+ user_commit_stu = "未提交"
+ end
+ user_start_time = e_user.start_at.present? ? e_user.start_at.strftime('%Y-%m-%d %H:%M') : "--"
+ user_end_time = e_user.end_at.present? ? e_user.end_at.strftime('%Y-%m-%d %H:%M') : "--"
+ user_student_id = user_info.student_id.present? ? user_info.student_id : "--"
+
+ user_option = [index+1,user_info.login,user_info.real_name, user_info.mail || '--',
+ user_student_id,user_course,user_commit_stu]
+ if ques_type_boolean
+ other_user_option = [user_obj_score,user_suj_score,user_score,user_start_time,user_end_time]
+ else
+ other_user_option = [user_score,user_start_time,user_end_time]
+ end
+ user_option = user_option + other_user_option
+ @user_columns.push(user_option)
+ end
+ end
+
+ #毕设选题的导出
+ def graduation_topic_to_xlsx(students,course)
+ @topic_head_cells = %w(序号 登录名 真实姓名 邮箱 学号 分班 课题名称 指导教师 教师职位 设计 论文 创作 生产/社会实际 结合科研 其它 真题 模拟题 纵向课题 横向课题 自选 新题 往届题,有新要求
+ 往届题,无新要求 课题来源单位 调研或实习地点 确认结果)
+
+ @topic_body_cells = []
+ if students.count > 0
+ students.each_with_index do |student, index|
+ user = student.user
+ student_topic = course.student_graduation_topics.user_topics_accept(user.id).first
+ if student_topic.present?
+ topic = student_topic.graduation_topic
+ else
+ student_topic = nil
+ topic = nil
+ end
+ w_1 = (index+1)
+ w_2 = user.login
+ w_3 = user.real_name
+ w_3_1 = user.mail
+ w_4 = user.user_extension.student_id
+ w_5 = student.course_group_name
+ w_6 = topic.present? ? topic.name : "--"
+ w_7 = topic.present? ? topic.teacher.full_name : "--"
+ w_8 = topic.present? ? topic.teacher.identity : "--"
+
+ if topic.present?
+ w_9 = topic.topic_type == 1 ? "√" : ""
+ w_10 = topic.topic_type == 2 ? "√" : ""
+ w_11 = topic.topic_type == 3 ? "√" : ""
+ w_12 = topic.topic_source == 1 ? "√" : ""
+ w_13 = topic.topic_source == 2 ? "√" : ""
+ w_14 = topic.topic_source == 3 ? "√" : ""
+ w_15 = topic.topic_property_first == 1 ? "√" : ""
+ w_16 = topic.topic_property_first == 2 ? "√" : ""
+ w_17 = topic.topic_property_second == 1 ? "√" : ""
+ w_18 = topic.topic_property_second == 2 ? "√" : ""
+ w_19 = topic.topic_property_second == 3 ? "√" : ""
+ w_20 = topic.topic_repeat == 1 ? "√" : ""
+ w_21 = topic.topic_repeat == 2 ? "√" : ""
+ w_22 = topic.topic_repeat == 3 ? "√" : ""
+ w_23 = topic.source_unit
+ w_24 = "#{topic.province}#{topic.city}"
+ else
+ w_9,w_10,w_11,w_12,w_13,w_14,w_15,w_16,w_17,w_18,w_19,w_20,w_21,w_22,w_23,w_24 = "--"
+ end
+ if student_topic.present?
+ w_25 = student_topic.status == 0 ? "待确认" : "已同意"
+ else
+ w_25 = "--"
+ end
+ student_info_array = [w_1,w_2,w_3,w_3_1,w_4,w_5,w_6,w_7,w_8,w_9,w_10,w_11,w_12,w_13,w_14,w_15,w_16,w_17,w_18,w_19,w_20,w_21,w_22,w_23,w_24,w_25]
+ @topic_body_cells.push(student_info_array)
+ end
+ end
+ end
+
+ def encode64(str)
+ Base64.urlsafe_encode64(str)
+ end
+
+ def decode64(str)
+ Base64.urlsafe_decode64(str)
+ end
+
+ # 检测文件大小是否超过500m
+ def checkfileSize(works)
+ file_count = 0
+ file_size = 0
+ works.each do |work|
+ file_count += work.attachments.count
+ work.attachments.each do |attach|
+ file_size += attach.filesize
+ end
+ end
+
+ if file_size > MAX_DOWN_SIZE
+ status = -1
+ elsif file_count > 0
+ status = 0
+ else
+ status = -2
+ end
+ status
+ end
+
+ def zip_homework_common homework_common, student_works
+ bid_homework_path = []
+ digests = []
+ student_works.each do |work|
+ unless work.attachments.empty?
+ out_file = zip_student_work_by_user(work, homework_common)
+
+ bid_homework_path << out_file.file_path
+ digests << out_file.file_digest
+ end
+ end
+
+ out_file_name = "#{Time.now.to_i}_#{homework_common.name}.zip"
+ out_file_name.gsub!(" ", "-")
+ out_file_name.gsub!("/", "_")
+ out_file = find_or_pack(homework_common, homework_common.user_id, digests.sort){
+ zipping(out_file_name, bid_homework_path, OUTPUT_FOLDER)
+ }
+
+ [{files:[out_file.file_path], count: 1, index: 1,
+ real_file: out_file.file_path,
+ file: File.basename(out_file.file_path),
+ base64file: encode64(File.basename(out_file.file_path)),
+ size:(out_file.pack_size / 1024.0 / 1024.0).round(2)
+ }]
+ end
+
+ def zip_student_work_by_user(work, object)
+ homeworks_attach_path = []
+ not_exist_file = []
+ filename = []
+ # 需要将所有homework.attachments遍历加入zip
+ digests = []
+ work.attachments.each do |attach|
+ if File.exist?(attach.diskfile)
+ homeworks_attach_path << attach.diskfile
+ digests << attach.digest
+ filename << attach.filename
+ else
+ not_exist_file << attach.filename
+ digests << 'not_exist_file'
+ filename << attach.filename
+ end
+ end
+
+ #单个文件的话,不需要压缩,只改名
+ if homeworks_attach_path.size == 1
+ out_file = find_or_pack(object, work.user_id, digests.sort){
+ des_path = "#{OUTPUT_FOLDER}/#{make_zip_name(work, filename.first)}_#{File.basename(homeworks_attach_path.first)}"
+ FileUtils.cp homeworks_attach_path.first, des_path
+ des_path
+ }
+ else
+ out_file = find_or_pack(object, work.user_id, digests.sort){
+ zipping("#{make_zip_name(work)}.zip",
+ homeworks_attach_path, OUTPUT_FOLDER, true, not_exist_file)
+ }
+ end
+ out_file
+
+ end
+
+ def find_or_pack(homework, user_id, digests)
+ raise "please given a pack block" unless block_given?
+
+ out_file = ZipPack.packed?(homework, user_id, digests.sort)
+
+ unless out_file && out_file.file_valid?
+ file = yield
+
+ ZipPack.where(container_id: homework.id, container_type: homework.class.to_s, user_id: user_id).delete_all
+
+ out_file = ZipPack.create(container_id: homework.id,
+ container_type: homework.class.to_s,
+ user_id: user_id,
+ file_digest: Educoder::Utils.digest(file),
+ file_path: file,
+ pack_size: File.size(file),
+ file_digests: digests.join(',')
+ )
+ else
+ out_file.pack_times = out_file.pack_times + 1
+ out_file.save
+ end
+
+ out_file
+ end
+
+ def make_zip_name(work, file_name="")
+ Rails.logger.info("######################file_name: #{file_name}")
+ name = file_name === "" ? "" : (file_name[0, file_name.rindex('.')]+"_")
+ "#{name}#{work.user.real_name}_#{((work.user.student_id.nil?) ? "" : work.user.student_id)}"
+ end
+
+ def zipping(zip_name_refer, files_paths, output_path, is_attachment=false, not_exist_file=[])
+ rename_zipfile = zip_name_refer ||= "#{Time.now.to_i.to_s}.zip"
+ # 文件名过长
+
+ if rename_zipfile.size > MAX_PATH
+ rename_zipfile = rename_zipfile[0,rename_zipfile.size-4][0,MAX_PATH-4] + rename_zipfile[-4,4]
+ end
+
+ zipfile_name = "#{output_path}/#{rename_zipfile}"
+
+ Dir.mkdir(File.dirname(zipfile_name)) unless File.exist?(File.dirname(zipfile_name))
+ Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile|
+ files_paths.each do |filename|
+ rename_file = File.basename(filename)
+ rename_file = filename_to_real( File.basename(filename)) if is_attachment
+
+ begin
+ zipfile.add(rename_file, filename)
+ rescue Exception => e
+ zipfile.get_output_stream('FILE_NOTICE.txt'){|os| os.write "该作品中有重复命名文件,请通过文件名学号和姓名信息进入该作业详细界面手动下载"}
+ next
+ end
+ end
+ unless not_exist_file.empty?
+ zipfile.get_output_stream('FILE_LOST.txt'){|os| os.write "以下文件无法成功下载,请联系相关人员重新上传:" +
+ not_exist_file.join(',').to_s}
+ end
+ end
+ zipfile_name
+ end
+
+ def filename_to_real(name)
+ attach = Attachment.find_by_disk_filename(name)
+ attach.filename
+ end
+
+end
diff --git a/app/helpers/games_helper.rb b/app/helpers/games_helper.rb
new file mode 100644
index 000000000..aff05904f
--- /dev/null
+++ b/app/helpers/games_helper.rb
@@ -0,0 +1,54 @@
+module GamesHelper
+
+ def game_code_init(game_id, path)
+ game_code = GameCode.where(:game_id => game_id, :path => path).first
+ GameCode.create(:game_id => game_id, :path => path) if game_code.blank?
+ end
+
+ # 获取目录下所有文件,返回一个文件名的数组 type是查看文件的类型image表示图片
+ # type [[1, "图片"], [2, "apk/exe"], [3, "txt"], [4, "html"]]
+ def get_dir_filename(path, type, game_id)
+ answer_picture = []
+ return answer_picture unless File.directory?(path)
+
+ images_suffix = %w(png jpg gif jpeg bmp pic)
+ Dir.foreach(path) do |file|
+ next if file == '.' || file == '..'
+
+ extension = file.split(".")[1].try(:downcase)
+ if images_suffix.include?(extension) && type == 1
+ answer_picture << file
+ @type = 'image'
+ elsif extension == 'html' && type == 4
+ answer_picture << file
+ @type = 'html'
+ elsif extension == 'txt' && type == 3
+ answer_picture << file
+ @contents = ''
+ f = File.open("#{path}/#{file}", 'r')
+ # ... process the file
+ f.each_line do |line|
+ if line.include?('Your score')
+ game = Game.find(game_id)
+ max_query_index = game.query_index - 1
+ a = line[11, line.length-1].try(:strip)
+ outputs = game.outputs.where(:query_index => max_query_index)
+ outputs.update_all(:text_scor => a) if outputs.present?
+ end
+ @contents += "#{line}"
+ end
+ f.close
+ @type = 'txt'
+ end
+ end
+
+ answer_picture
+ end
+
+ def evaluate_actual_output test_set
+ if test_set.has_attribute?(:code)
+ test_set.try(:compile_success).to_s == '0' && test_set.code.to_s == "-1" ?
+ "编译失败,请在测试结果中查看具体的错误信息" : test_set.try(:actual_output)
+ end
+ end
+end
diff --git a/app/helpers/graduation_tasks_helper.rb b/app/helpers/graduation_tasks_helper.rb
new file mode 100644
index 000000000..5253b0d56
--- /dev/null
+++ b/app/helpers/graduation_tasks_helper.rb
@@ -0,0 +1,134 @@
+module GraduationTasksHelper
+ include CoursesHelper
+ # 教师评阅
+ def teacher_comment task, user_id
+ [{ id: 0 ,name: "未评", count: task.uncomment_count(user_id)}, {id: 1, name: "已评", count: task.comment_count(user_id)}]
+ end
+
+ # 作品状态
+ def task_status task, user_id
+ [{id: 0, name: "未提交", count: task.unfinished_count(user_id)},
+ {id: 1, name: "按时提交", count: task.finished_count(user_id)},
+ {id: 2, name: "延时提交", count: task.delay_finished_count(user_id)}]
+ end
+
+ # 交叉评阅
+ def cross_comment task, user_id
+ if task.cross_comment
+ [{id: 1, name: "只看我的交叉评阅", count: task.graduation_work_comment_assignations.myself(user_id).count}]
+ else
+ []
+ end
+ end
+
+ def task_curr_status task, course
+ result = {}
+ status = []
+ time = ""
+
+ if course.try(:is_end)
+ status << "已结束"
+ time = course.end_date.present? ? course.end_date.strftime("%Y-%m-%d") : ""
+ else
+ if task.status > 1 && task.allow_late && (task.late_time.nil? || task.late_time > Time.now)
+ status << "补交中"
+ end
+
+ case task.status
+ when 0
+ status << "未发布"
+ time = task.publish_time.present? ? "将于 #{format_time(task.publish_time)} 发布" : "创建于#{time_from_now(task.created_at)}"
+ when 1
+ if task.end_time && task.end_time >= Time.now
+ status << "提交中"
+ time = how_much_time(task.end_time)
+ end
+ when 2
+ status << "评阅中"
+ time = task.comment_time.present? ? how_much_time(task.comment_time) : course.end_date.present? ? how_much_time(course.end_date.end_of_day) : ""
+ when 3
+ status << "交叉评阅中"
+ time = course.end_date.present? ? how_much_time(course.end_date.end_of_day) : ""
+ end
+
+ status << "未开启补交" if (!task.allow_late && task.status != 0) #6.11 -hs 新增status不等于0
+
+ # 如果还在补交阶段则显示补交结束时间
+ if task.status > 1 && task.allow_late && task.late_time && task.late_time > Time.now
+ time = how_much_time(task.late_time)
+ end
+ end
+
+ result[:status] = status
+ result[:time] = time
+ result
+ end
+
+ # 作品数统计:type: 1 已提交 0 未提交
+ def grduationwork_count task, type
+ works = task.graduation_works
+ type == 1 ? works.where("work_status !=?", 0).size : works.where("work_status =?", 0).size
+ end
+
+ # 普通/分组 作业作品状态数组
+ def graduation_work_status task, user_id, course
+ status = []
+ work = task.graduation_works.find_by(user_id: user_id)
+
+ work = work || GraduationWork.create(graduation_task_id: task.id, user_id: user_id)
+ late_time = task.late_time || course.end_date
+
+ if course.is_end && work && work.work_status > 0
+ status << "查看作品"
+ elsif !course.is_end
+ if task.publish_time && task.publish_time < Time.now
+ # 作业未截止时
+ if task.end_time > Time.now
+ if task.task_type == 2 && task.base_on_project
+ if work.project_id.nil? || work.project_id == 0
+ status << "创建项目"
+ status << "关联项目"
+ elsif work.work_status == 0
+ status << "取消关联"
+ status << "提交作品"
+ else
+ status << "修改作品"
+ end
+ else
+ if work.work_status == 0
+ status << "提交作品"
+ else
+ status << "修改作品"
+ end
+ end
+
+ # 补交阶段
+ elsif task.allow_late && (late_time.nil? || late_time > Time.now)
+ if task.task_type == 2 && task.base_on_project
+ if work.project_id.nil? || work.project_id == 0
+ status << "创建项目"
+ status << "关联项目"
+ elsif work.work_status == 0
+ status << "取消关联"
+ status << "补交作品"
+ else
+ status << "补交附件"
+ status << "查看作品"
+ end
+ else
+ if work.work_status == 0
+ status << "补交作品"
+ else
+ status << "补交附件"
+ status << "查看作品"
+ end
+ end
+
+ # 匿评阶段
+ elsif work.work_status != 0
+ status << "查看作品"
+ end
+ end
+ end
+ end
+end
diff --git a/app/helpers/graduation_topics_helper.rb b/app/helpers/graduation_topics_helper.rb
new file mode 100644
index 000000000..ae15d201e
--- /dev/null
+++ b/app/helpers/graduation_topics_helper.rb
@@ -0,0 +1,30 @@
+module GraduationTopicsHelper
+
+ # 课题类型
+ def topic_type
+ [{id: 1, name: "设计"}, {id: 2, name: "论文"}, {id: 3, name: "创作"}]
+ end
+
+ # 课程来源
+ def topic_source
+ [{id: 1, name: "生产/社会实际"}, {id: 2, name:"结合科研"}, {id: 3, name: "其它"}]
+ end
+
+ # 课题性质1
+ def topic_property_first
+ [{id: 1, name: "真题"}, {id: 2, name:"模拟题"}]
+ end
+
+ # 课题性质2
+ def topic_property_second
+ [{id: 1, name: "纵向课题"}, {id: 2, name:"横向课题"}, {id: 3, name: "自选"}]
+ end
+
+ # 课题重复
+ def topic_repeat
+ [{id: 1, name: "新题"}, {id: 2, name:"往届题,有新要求"}, {id: 3, name: "往届题,无新要求"}]
+ end
+
+
+
+end
diff --git a/app/helpers/graduation_works_helper.rb b/app/helpers/graduation_works_helper.rb
new file mode 100644
index 000000000..177720d24
--- /dev/null
+++ b/app/helpers/graduation_works_helper.rb
@@ -0,0 +1,21 @@
+module GraduationWorksHelper
+ include GraduationTasksHelper
+
+ # 作品最终成绩
+ # 参数: work作品, current_user用户,course_identity用户在课堂的身份
+ def work_final_score work, current_user, course_identity
+ work_score =
+ if work.work_score.nil?
+ "--"
+ else
+ if work.check_score_power? current_user, course_identity
+ format("%.1f", work.work_score < 0 ? 0 : work.work_score.round(1))
+ else
+ "**"
+ end
+ end
+ # work_score 最终成绩; late_penalty 迟交扣分; final_score 最终评分
+ {username: work.user.full_name, login: work.user.login, work_score: work_score, final_score: work.final_score,
+ late_penalty:work.late_penalty }
+ end
+end
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
new file mode 100644
index 000000000..cfc942e67
--- /dev/null
+++ b/app/helpers/home_helper.rb
@@ -0,0 +1,2 @@
+module HomeHelper
+end
diff --git a/app/helpers/homework_commons_helper.rb b/app/helpers/homework_commons_helper.rb
new file mode 100644
index 000000000..0d166729c
--- /dev/null
+++ b/app/helpers/homework_commons_helper.rb
@@ -0,0 +1,240 @@
+module HomeworkCommonsHelper
+
+ # 未发布时非老师角色不能访问,发布后非课堂成员不能访问未公开的作业,学生需要考虑分班设置的作业是否已发布
+ def homework_publish
+ if (@user_course_identity >= Course::STUDENT && (@homework.publish_time.nil? || @homework.publish_time > Time.now)) ||
+ (@user_course_identity > Course::STUDENT && (!@homework.unified_setting || @course.is_public == 0 || !@homework.is_public))
+ tip_exception(403,"没有操作权限")
+ elsif @user_course_identity == Course::STUDENT && !@homework.unified_setting
+ member = @course.course_member(current_user.id)
+ group_setting = @homework.homework_group_settings.find_by(course_group_id: member.try(:course_group_id))
+ if group_setting.try(:publish_time).nil? || group_setting.try(:publish_time) > Time.now
+ tip_exception(403,"没有操作权限")
+ end
+ end
+ end
+
+ def homework_curr_status homework_common, identity, course, member, teacher_course_groups
+ result = {}
+ status = []
+ time = ""
+ time_status = nil
+ if course.try(:is_end)
+ status << "已结束"
+ time = course.end_date.strftime("%Y-%m-%d")
+ time_status = 6
+ else
+ if homework_common.end_time && homework_common.end_time < Time.now && homework_common.allow_late &&
+ (homework_common.late_time.nil? || homework_common.late_time > Time.now)
+ status << "补交中"
+ end
+
+ ho_detail_manual = homework_common.homework_detail_manual
+ if ho_detail_manual
+ # 作业状态大于“提交”状态时,不用考虑分班权限
+ if ho_detail_manual.comment_status > 1
+ case ho_detail_manual.comment_status
+ when 3
+ if ho_detail_manual.evaluation_end && ho_detail_manual.evaluation_end > Time.now
+ status << "匿评中"
+ time = how_much_time(ho_detail_manual.evaluation_end)
+ time_status = 3
+ end
+ when 4
+ if ho_detail_manual.appeal_time && ho_detail_manual.appeal_time > Time.now
+ status << "申诉中"
+ time = how_much_time(ho_detail_manual.appeal_time)
+ time_status = 4
+ end
+ when 2, 5, 6
+ status << "评阅中"
+ time = course.end_date.present? ? how_much_time(course.end_date.end_of_day) : ""
+ time_status = 5
+ end
+
+ # 如果还在补交阶段则显示补交结束时间
+ if homework_common.end_time && homework_common.end_time < Time.now && homework_common.allow_late &&
+ homework_common.late_time && homework_common.late_time > Time.now
+ time = how_much_time(homework_common.late_time)
+ time_status = 2
+ end
+ else
+ # member = course.course_members.find_by(user_id: user.id, is_active: 1)
+ # teacher_course_groups = member.try(:teacher_course_groups)
+ # identity = user.course_identity(course)
+
+ # 作业统一设置、游客身份、超级管理员、分班权限不限的老师身份
+ if homework_common.unified_setting || identity > Course::STUDENT || identity == Course::ADMIN ||
+ (identity < Course::STUDENT && teacher_course_groups.size == 0)
+ case ho_detail_manual.comment_status
+ when 0
+ status << "未发布"
+ time = homework_common.publish_time.present? ? "将于 #{format_time(homework_common.publish_time)} 发布" : "创建于#{time_from_now(homework_common.created_at)}"
+ time_status = 0
+ when 1
+ if homework_common.end_time && homework_common.end_time >= Time.now
+ status << "提交中"
+ time = how_much_time(homework_common.end_time)
+ time_status = 1
+ elsif homework_common.end_time && homework_common.end_time < Time.now
+ time_status = 5
+ if homework_common.allow_late && (homework_common.late_time.nil? || homework_common.late_time >= Time.now)
+ time = how_much_time(homework_common.late_time)
+ time_status = 2
+ end
+ status << "评阅中"
+ end
+ end
+ else
+ # 未分班的学生始终显示“未发布”(按理不会来到这个判断)
+ if member.role == "STUDENT" && member.course_group_id == 0
+ status << "未发布"
+ time = ""
+ time_status = 0
+ else
+ if member.role == "STUDENT"
+ setting = homework_common.homework_group_settings.find_by(course_group_id: member.course_group_id)
+ min_publish_time = setting.try(:publish_time)
+ max_end_time = setting.try(:end_time)
+ else
+ # 多个分班权限的取最小publish_time,最大end_time
+ min_publish_time = homework_common.homework_group_settings.where.not(publish_time: nil).
+ where(course_group_id: teacher_course_groups.pluck(:course_group_id)).pluck(:publish_time).min
+ max_end_time = homework_common.homework_group_settings.where.not(end_time: nil).
+ where(course_group_id: teacher_course_groups.pluck(:course_group_id)).pluck(:end_time).max
+ end
+
+ if min_publish_time.nil?
+ status << "未发布"
+ time = "创建于#{time_from_now(homework_common.created_at)}"
+ time_status = 0
+ elsif min_publish_time > Time.now
+ status << "未发布"
+ time = "将于 #{format_time(min_publish_time)} 发布"
+ time_status = 0
+ elsif max_end_time.present? && max_end_time > Time.now #6.14 -hs 添加present?
+ status << "提交中"
+ time = how_much_time(max_end_time)
+ time_status = 1
+ elsif homework_common.allow_late && (homework_common.late_time.nil? || homework_common.late_time >= Time.now)
+ status << "评阅中"
+ time = how_much_time(homework_common.late_time)
+ time_status = 2
+ else
+ status << "评阅中"
+ time = ""
+ time_status = 5
+ end
+ end
+ end
+ status << "未开启补交" if !homework_common.allow_late && time_status != 0
+ end
+ end
+ end
+ # 如果作业状态都没有的话,在课堂结束前,都显示评阅中
+ if status.blank?
+ status << "评阅中"
+ end
+ result[:status] = status
+ result[:time] = time
+ result[:time_status] = time_status
+ result
+ end
+
+
+ # 阶段剩余时间
+ def left_time homework, user_id
+ setting = homework.homework_group_setting(user_id)
+ if setting.publish_time && setting.publish_time < Time.now
+ if setting.end_time > Time.now
+ status = "剩余提交时间"
+ time = "#{how_much_time(setting.end_time)}"
+ else
+ ho_detail_manual = homework.homework_detail_manual
+ case ho_detail_manual.comment_status
+ when 3
+ if ho_detail_manual.evaluation_end && ho_detail_manual.evaluation_end > Time.now
+ status = "剩余匿评时间"
+ time = "#{how_much_time(ho_detail_manual.evaluation_end)}"
+ end
+ when 4
+ if ho_detail_manual.appeal_time && ho_detail_manual.appeal_time > Time.now
+ status = "剩余申诉时间"
+ time = "#{how_much_time(ho_detail_manual.appeal_time)}"
+ end
+ else
+ if homework.allow_late && homework.late_time && homework.late_time >= Time.now
+ status = "剩余补交时间"
+ time = "#{how_much_time(homework.late_time)}"
+ end
+ end
+ end
+ end
+ {status: status, time: time}
+ end
+
+ # 作品数统计:type: 1 已提交 0 未提交
+ def studentwork_count homework_common, type, user_id
+ student_works = homework_common.teacher_works(user_id)
+ type == 1 ? student_works.select{|work| work.work_status != 0}.size : student_works.select{|work| work.work_status = 0}.size
+ end
+
+ # 上次查重的时间
+ def last_review_time homework_common, course_group
+ review = homework_common.homework_group_reviews.where(:course_group_id => course_group.id).last
+ review ? (format_time review.created_at) : "--"
+ end
+
+ # 分班的有效作品数
+ def homework_works_count homework_common, course_group
+ myshixun_ids = homework_common.student_works.where(user_id: course_group.course_members.pluck(:user_id)).pluck(:myshixun_id)
+ myshixuns = Myshixun.joins(:games).where(id: myshixun_ids).where("games.status = ?", 2).count
+ end
+
+ # 未分班的有效作品数
+ def homework_ungroup_works_count homework_common, user_ids
+ myshixun_ids = homework_common.student_works.where(user_id: user_ids).pluck(:myshixun_id)
+ myshixuns = Myshixun.joins(:games).where(id: myshixun_ids).where("games.status = ?", 2).count
+ end
+
+ def ungroup_last_review_time homework_common
+ review = homework_common.homework_group_reviews.where(:course_group_id => 0).last
+ review ? (format_time review.created_at) : "--"
+ end
+
+ def work_finished_time challenge_id, user_id
+ Challenge.find(challenge_id).games.find_by(user_id: user_id)
+ end
+
+ def challenge_setting homework, challenge_id
+ homework.homework_challenge_settings.find_by(challenge_id: challenge_id)
+ end
+
+ def group_homework_setting homework, group_id
+ homework.homework_group_settings.find_by(course_group_id: group_id)
+ end
+
+ def homework_st_proportion homework
+ (1.0 - homework.homework_detail_manual.te_proportion - homework.homework_detail_manual.ta_proportion).round(2)
+ end
+
+ def teacher_comment homework, user_id
+ [{ id: 0 ,name: "未评", count: homework.uncomment_count(user_id)}, {id: 1, name: "已评", count: homework.comment_count(user_id)}]
+ end
+
+ # 作品状态
+ def homework_status homework, user_id
+ [{id: 0, name: "未提交", count: homework.unfinished_count(user_id)},
+ {id: 1, name: "按时提交", count: homework.finished_count(user_id)},
+ {id: 2, name: "延时提交", count: homework.delay_finished_count(user_id)}]
+ end
+
+ # 作品分数的显示
+ def work_score_format score, current_user, score_open
+ score.nil? ? "--" : (current_user || score_open) ? number_with_precision(score, precision: 1) : "**"
+ end
+
+ def anon_comments user, work_id
+ StudentWorksScore.where(student_work_id: work_id, reviewer_role: 3, user_id: user.id)
+ end
+end
diff --git a/app/helpers/memos_helper.rb b/app/helpers/memos_helper.rb
new file mode 100644
index 000000000..7df887a4b
--- /dev/null
+++ b/app/helpers/memos_helper.rb
@@ -0,0 +1,2 @@
+module MemosHelper
+end
diff --git a/app/helpers/messages_helper.rb b/app/helpers/messages_helper.rb
new file mode 100644
index 000000000..84d6b08d2
--- /dev/null
+++ b/app/helpers/messages_helper.rb
@@ -0,0 +1,67 @@
+module MessagesHelper
+
+ def by_user_liked?(obj, user)
+ obj.praise_treads.user_liker(user).present?
+ end
+ # 置顶降序排序(置顶排最前面)
+ def sort_by_sticky(messages)
+ messages = messages.sort_by {|message| -message.sticky } if messages.map(&:sticky).include?(1)
+ return messages
+ end
+
+ # 根据回复数(包含二级回复)排序
+ def sort_by_all_replies(sort, sort_type, arr)
+ return arr unless sort_type == "hot"
+ logger.info("####====> order by replies")
+ arr.each do |message|
+ message.total_replies_count = message.replies_count + message.children.sum(:replies_count)
+ end
+ return arr.sort_by { |msg| sort == 1 ? msg.total_replies_count : -msg.total_replies_count }
+ end
+
+ def validate_delete_params
+ return normal_status(403, "") unless current_user.teacher_of_course?(@board.course)
+ return normal_status(2, "缺少ids参数!") if params[:ids].blank?
+ return normal_status(2, "参数ids格式不对!") unless params[:ids].is_a? Array
+ end
+
+ def validate_move_params
+ return normal_status(2, "参数ids不能为空!") if params[:ids].blank?
+ return normal_status(2, "参数ids格式错误!") unless params[:ids].is_a? Array
+ return normal_status(2, "参数to_board_id不能为空!") if params[:to_board_id].blank?
+ end
+
+ def message_validate_create_params
+ msg = if params[:select_board_id].blank?
+ "目录id不能为空!"
+ elsif params[:subject].blank?
+ "帖子标题不能为空!"
+ elsif params[:content].blank?
+ "帖子内容不能为空!"
+ elsif params.has_key?(:attachment_ids) && !params[:attachment_ids].is_a?(Array)
+ "参数attachment_ids格式错误!"
+ else
+ nil
+ end
+ normal_status(2, msg) unless msg.nil?
+ end
+
+ def validate_update_params
+ normal_status(2, "目录id不能为空!") if params.has_key?(:select_board_id) && params[:select_board_id].blank?
+ normal_status(2, "帖子标题不能为空!") if params.has_key?(:subject) && params[:subject].blank?
+ normal_status(2, "帖子内容不能为空!") if params.has_key?(:content) && params[:content].blank?
+ end
+
+ def validate_send_message_to_course_params
+ return normal_status(2, "ids参数不能为空!") if params[:ids].blank?
+ return normal_status(2, "参数ids格式不对!") unless params[:ids].is_a? Array
+ return normal_status(2, "to_course_ids参数不能为空!") if params[:to_course_ids].blank?
+ return normal_status(2, "参数to_course_ids格式不对!") unless params[:to_course_ids].is_a? Array
+ end
+
+ def validate_page_size
+ return if !params.has_key?(:page_size)
+ return normal_status(0, "每页请求的数量只能为5-50") if params[:page_size].to_i < 5 || params[:page_size].to_i > 50
+ end
+
+end
diff --git a/app/helpers/myshixuns_helper.rb b/app/helpers/myshixuns_helper.rb
new file mode 100644
index 000000000..650f32f40
--- /dev/null
+++ b/app/helpers/myshixuns_helper.rb
@@ -0,0 +1,2 @@
+module MyshixunsHelper
+end
diff --git a/app/helpers/poll_votes_helper.rb b/app/helpers/poll_votes_helper.rb
new file mode 100644
index 000000000..723f56782
--- /dev/null
+++ b/app/helpers/poll_votes_helper.rb
@@ -0,0 +1,2 @@
+module PollVotesHelper
+end
diff --git a/app/helpers/polls_helper.rb b/app/helpers/polls_helper.rb
new file mode 100644
index 000000000..5b4702174
--- /dev/null
+++ b/app/helpers/polls_helper.rb
@@ -0,0 +1,124 @@
+module PollsHelper
+
+ #获取试卷的已答/未答人数
+ def get_poll_answers(poll_users)
+ @commit_poll_users = poll_users.commit_by_status(1) #当前老师的全部学生中已提交的
+ @poll_answers = @commit_poll_users.present? ? @commit_poll_users.count : 0 #表示已经提交了的用户
+ course_all_members_count = poll_users.present? ? poll_users.count : 0
+ @poll_unanswers = (course_all_members_count - @poll_answers)
+ end
+
+ def poll_votes_count(votes,user_ids)
+ votes.find_current_vote("user_id",user_ids.uniq).size
+ end
+
+ #公用tab页的相关信息
+ def poll_common_header(is_teacher_or,poll)
+ poll_url_status = []
+ if is_teacher_or == 1 #当为老师的
+ common_tabs = %w(1 2 3 4)
+ else
+ if poll.show_result #开启了公开统计
+ common_tabs = %w(1 2)
+ else
+ common_tabs = %w(1)
+ end
+ end
+
+ common_tabs.each do |c|
+ if request.url.include?("polls_lists")
+ active_status = 1
+ elsif request.url.include?("commit_result")
+ active_status = 1
+ elsif request.url.include?("poll_setting")
+ active_status = 1
+ elsif request.url == poll_path(poll)
+ active_status = 1
+ else
+ active_status = 0
+ end
+ common_tab = {
+ :common_tab => c,
+ :active_status => active_status
+ }
+ poll_url_status.push(common_tab)
+ end
+ poll_url_status
+ end
+
+ #poll的列表页的显示
+ def poll_index_show(poll,course,is_teacher_or,user)
+ # poll_all_users = poll.poll_users
+ if course.is_end #课堂停止后,试卷显示为已结束
+ poll_status = 4
+ elsif is_teacher_or == 1 || is_teacher_or == 0 || poll.unified_setting
+ #当前为老师(非课堂成员、统一设置)的时候,显示的是老师身份的对应试卷的状态,因为该试卷,可能对应老师的多个分班
+ poll_status = poll.polls_status
+ else
+ poll_status = poll.get_poll_status(user.id) #当前用户查看的试卷的发布状态
+ end
+
+ if is_teacher_or == 1
+ poll_users_list = poll.all_poll_users(user.id) #当前老师所在班级的全部学生
+ get_poll_answers(poll_users_list)
+ ex_pb_time = poll.get_poll_times(user.id,true)
+ poll_publish_time = ex_pb_time[:publish_time]
+ poll_end_time = ex_pb_time[:end_time]
+ current_status = 3
+ lock_icon = 1 #不显示锁图标
+ elsif is_teacher_or == 2
+ poll_users_list = poll.get_poll_exercise_users
+ get_poll_answers(poll_users_list) # 未答和已答的
+ # get_poll_answers(poll_all_users)
+ ex_pb_time = poll.get_poll_times(user.id,false)
+ poll_publish_time = ex_pb_time[:publish_time]
+ poll_end_time = ex_pb_time[:end_time]
+ current_status = poll.check_user_votes_status(user)
+ lock_icon = 1 #不显示锁图标
+ else
+ poll_users_list = poll.get_poll_exercise_users
+ get_poll_answers(poll_users_list) # 未答和已答的
+ poll_publish_time = poll.publish_time
+ poll_end_time = poll.end_time
+ current_status = 4
+ if poll.is_public
+ lock_icon = 1 #非课堂成员,但是试卷为公开的,不加锁
+ else
+ lock_icon = 0 #显示锁图标
+ end
+ end
+ {
+ "publish_time":poll_publish_time,
+ "end_time":poll_end_time,
+ "poll_answer":@poll_answers,
+ "poll_unanswer":@poll_unanswers,
+ "current_status":current_status,
+ "lock_icon":lock_icon,
+ "polls_status":poll_status
+ }
+ end
+
+ #问卷列表页的用户信息显示
+ def poll_user_info(poll_user,course_members,course)
+ poll = poll_user.poll
+ user = poll_user.user
+ user_student_id = user.student_id
+
+ if poll.un_anonymous #是否开启实名认证
+ poll_user_name = user.real_name
+ else
+ poll_user_name = user.nickname
+ end
+ course_member = course_members.find_by(user_id:user.id)
+ current_user_group_id = course_members.present? ? course_member.course_group_id : nil
+ course_group = course.course_groups.find_by_id(current_user_group_id)
+ {
+ "user_name":poll_user_name,
+ "user_id": user.id,
+ "student_id":user_student_id,
+ "group_name":course_group.try(:name),
+ "group_id":course_group.try(:id),
+ "login":user.login
+ }
+ end
+end
diff --git a/app/helpers/praise_tread_helper.rb b/app/helpers/praise_tread_helper.rb
new file mode 100644
index 000000000..2e13973e7
--- /dev/null
+++ b/app/helpers/praise_tread_helper.rb
@@ -0,0 +1,2 @@
+module PraiseTreadHelper
+end
diff --git a/app/helpers/question_banks_helper.rb b/app/helpers/question_banks_helper.rb
new file mode 100644
index 000000000..2d129eed5
--- /dev/null
+++ b/app/helpers/question_banks_helper.rb
@@ -0,0 +1,2 @@
+module QuestionBanksHelper
+end
diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
new file mode 100644
index 000000000..05ffdf1cc
--- /dev/null
+++ b/app/helpers/repositories_helper.rb
@@ -0,0 +1,2 @@
+module RepositoriesHelper
+end
diff --git a/app/helpers/schools_helper.rb b/app/helpers/schools_helper.rb
new file mode 100644
index 000000000..caddacedc
--- /dev/null
+++ b/app/helpers/schools_helper.rb
@@ -0,0 +1,2 @@
+module SchoolsHelper
+end
diff --git a/app/helpers/shixuns_helper.rb b/app/helpers/shixuns_helper.rb
new file mode 100644
index 000000000..71bc833d4
--- /dev/null
+++ b/app/helpers/shixuns_helper.rb
@@ -0,0 +1,154 @@
+module ShixunsHelper
+
+ def level_to_s(level)
+ %W(初级 中级 高级 顶级)[level-1]
+ end
+
+ #难度
+ def diff_to_s(trainee)
+ %W(初级学员 中级学员 高级学员 顶级学员)[trainee-1]
+ end
+
+ #1. 未发布
+ def status_to_s(status)
+ %W(未发布 已发布 已关闭)[status-1]
+ end
+
+ # 已完成实训所获得的经验值
+ def myshixun_exp myshixun
+ score = 0
+ myshixun.games.each do |game|
+ score += game.final_score.to_i < 0 ? 0 : game.challenge.score.to_i
+ end
+ score
+ end
+
+ # 已完成实训所消耗的时间
+ def myshixun_spend_time myshixun
+ time = myshixun.games.map(&:cost_time).sum
+ time
+ end
+
+ # 已完成实训的准确率
+ def myshixun_accuracy myshixun
+ accuracy = 0
+ count = 0
+ myshixun.games.each do |game|
+ count += 1
+ accuracy += game.accuracy ? game.accuracy : 0
+ end
+ accuracy.to_f / count
+ end
+
+ # 实训的最终通关时间
+ def myshixun_done_time myshixun
+ time = ""
+ if myshixun.status == 1 || myshixun.games.where(:status => 2).count == myshixun.shixun.challenges.count
+ time = myshixun.games.where(:status => 2).map(&:end_time).max
+ end
+ time
+ end
+
+ def had_passed_count
+
+ end
+
+ #显示项目配置菜单
+ def show_project_memu user
+ if user.allowed_to?(:edit_project, @shixun)
+ result = "edit_project"
+ elsif user.allowed_to?(:manage_members, @shixun)
+ result = "training_task"
+ elsif user.allowed_to?(:manage_versions, @shixun)
+ result = "manage_versions"
+ elsif user.allowed_to?(:manage_repository, @shixun)
+ result = "manage_repository"
+ end
+ result
+ end
+
+ def show_shixun_mirror shixun
+ mirror_name = ""
+ shixun.mirror_repositories.try(:each) do |mirror|
+ if mirror_name.blank?
+ mirror_name = mirror.type_name
+ else
+ mirror_name = "#{mirror_name};#{mirror.type_name}"
+ end
+ end
+ return mirror_name
+ end
+
+ def generate_script shixun, script
+ if script.present?
+ source_class_name = []
+ challenge_program_name = []
+ shixun.challenges.map(&:exec_path).each do |exec_path|
+ challenge_program_name << "\"#{exec_path}\""
+ if shixun.mirror_name.try(:first) == "Java"
+ if exec_path.nil? || exec_path.split("src/")[1].nil?
+ source = ""
+ else
+ source = "\"#{exec_path.split("src/")[1].split(".java")[0]}\""
+ end
+ source_class_name << source.gsub("/", ".") if source.present?
+ end
+ end
+ script = if script.include?("sourceClassName") && script.include?("challengeProgramName")
+ script.gsub("CHALLENGEPROGRAMNAMES","#{challenge_program_name.reject(&:blank?).join(" ")}").gsub("SOURCECLASSNAMES", "#{source_class_name.reject(&:blank?).join(" ")}")
+ else
+ script.gsub("CHALLENGEPROGRAMNAMES","#{challenge_program_name.reject(&:blank?).join(" ")}").gsub("SOURCECLASSNAMES", "#{challenge_program_name.reject(&:blank?).join(" ")}")
+ end
+ end
+ return script
+ end
+
+ def had_passed_challenge challenge
+ Game.where(:challenge_id => challenge.id, :status => 2).limit(3).reorder("final_score desc")
+ end
+
+ def finished_num challenge
+ Game.finished_num(challenge.id).count
+ end
+
+ def doing_num challenge
+ Game.doing_num(challenge.id).count
+ end
+
+ def challenge_status(challenge_id, user_id)
+ Game.where(challenge_id: challenge_id, user_id: user_id).limit(1).try(:status) || 0
+ end
+
+ # 若实训关卡位置,关卡数等信息发生变化则需要修改脚本内容
+ def modify_shixun_script shixun, script
+ if script.present?
+ source_class_name = []
+ challenge_program_name = []
+ shixun.challenges.map(&:exec_path).each do |exec_path|
+ challenge_program_name << "\"#{exec_path}\""
+ if shixun.main_mirror_name.try(:first) == "Java"
+ if exec_path.nil? || exec_path.split("src/")[1].nil?
+ source = "\"\""
+ else
+ source = "\"#{exec_path.split("src/")[1].split(".java")[0]}\""
+ end
+ source_class_name << source.gsub("/", ".") if source.present?
+ elsif shixun.main_mirror_name.try(:first) == "C#"
+ if exec_path.nil? || exec_path.split(".")[1].nil?
+ source = "\"\""
+ else
+ source = "\"#{exec_path.split(".")[0]}.exe\""
+ end
+ source_class_name << source if source.present?
+ end
+ end
+ script = if script.include?("sourceClassName") && script.include?("challengeProgramName")
+ script.gsub(/challengeProgramNames=\(.*\)/,"challengeProgramNames=\(#{challenge_program_name.reject(&:blank?).join(" ")}\)").gsub(/sourceClassNames=\(.*\)/, "sourceClassNames=\(#{source_class_name.reject(&:blank?).join(" ")}\)")
+ else
+ script.gsub(/challengeProgramNames=\(.*\)/,"challengeProgramNames=\(#{challenge_program_name.reject(&:blank?).join(" ")}\)").gsub(/sourceClassNames=\(.*\)/, "sourceClassNames=\(#{challenge_program_name.reject(&:blank?).join(" ")}\)")
+ end
+ end
+ return script
+ end
+
+end
\ No newline at end of file
diff --git a/app/helpers/stages_helper.rb b/app/helpers/stages_helper.rb
new file mode 100644
index 000000000..de6501fa6
--- /dev/null
+++ b/app/helpers/stages_helper.rb
@@ -0,0 +1,59 @@
+module StagesHelper
+
+ # 章节实训的通关情况
+ def stage_myshixun_status shixun, user
+ myshixun = Myshixun.where(user_id: user.id, shixun_id: shixun.id).first
+ myshixun.try(:status) == 1 ? 1 : 0
+ end
+
+ # 实训路径详情列表,右侧实训的状态显示栏
+ def stage_shixun_status subject_status, shixun_status, shixun_hidden
+ status = if shixun_hidden
+ '暂未公开'
+ else
+ if subject_status < 2
+ case shixun_status
+ when 0, 1
+ '暂未公开'
+ when 2
+ '已发布'
+ when 3
+ '已关闭'
+ end
+ else
+ if shixun_status != 2
+ case shixun_status
+ when 0, 1
+ '暂未公开'
+ when 3
+ '已关闭'
+ end
+ else
+ ''
+ end
+ end
+ end
+ return status
+ end
+
+ # 开启挑战的path
+ def stage_tpi_path shixun, user, subject_id
+ path = ""
+ if shixun.status == 2 || user.manager_of_shixun?(shixun)
+ myshixun = Myshixun.where(user_id: user.id, shixun_id: shixun.id).first
+ if myshixun.present?
+ if user.try(:mail).blank?
+ path = security_settings_path
+ elsif shixun.challenges_count > 0
+ is_modify = ShixunModify.where(:myshixun_id => myshixun.try(:id), :shixun_id => shixun.try(:id), :status => 1).first
+ if is_modify.blank?
+ path = shixun_exec_shixun_path(shixun, :is_subject => subject_id)
+ else
+ path = myshixun_reset_myshixun_path(myshixun, :is_subject => subject_id)
+ end
+ end
+ end
+ end
+ return path
+ end
+end
diff --git a/app/helpers/student_works_helper.rb b/app/helpers/student_works_helper.rb
new file mode 100644
index 000000000..93a1a3f9a
--- /dev/null
+++ b/app/helpers/student_works_helper.rb
@@ -0,0 +1,165 @@
+module StudentWorksHelper
+ # 学生作业是否迟交
+ def student_work_is_delay? homework_common, game
+ (Time.now > homework_common.end_time && game.end_time.blank?) || (game.end_time.present? && game.end_time > homework_common.end_time)
+ end
+
+ # 关卡的完成状态:提交截止(不允许补交)后或补交截止后通关的是无效状态,允许补交且在补交阶段内通关的是延时状态,提交截止前完成的是正常状态
+ # 0:未通关 1:正常 2:延时 3:无效
+ def game_status game, homework
+ if game.status == 2
+ homework_setting = homework.homework_group_setting game.user_id
+ status = homework_setting.end_time >= game.end_time ? 1 :
+ (homework.allow_late && homework.late_time > game.end_time) ? 2 : 3
+ else
+ status = 0
+ end
+ status
+ end
+
+ # 作业的开启时间
+ def myshixun_open_time game
+ game.open_time ? (format_time game.open_time) : "--"
+ end
+
+ # 作业完成时间
+ def finished_time end_time
+ end_time ? (format_time end_time) : "--"
+ end
+
+ # 作业耗时
+ def time_consuming game
+ game.end_time.blank? ? "--" : (game_spend_time game.cost_time)
+ end
+
+ # 用户个人实训总得分:user_total_score;
+ # consume_time通关的关卡总耗时间;
+ # all_tine实训的总耗时
+ # evaluate_count: 每次总评测次数;
+ # eff_score:用户得分/总评测次数;
+ # round_size:log(所有课堂实训最大评测次数/当前实训评测次数)
+
+ def student_efficiency(homework_common, work)
+ myshixun_ids = homework_common.student_works.pluck(:myshixun_id)
+ myshixuns = Myshixun.where(id: myshixun_ids).includes(games: [:outputs])
+ #student_works_user_id = homework_common.student_works.pluck(:user_id).uniq
+ #shixun = homework_common.shixuns.first
+ #logger.info("#########shixun_id: ###{shixun.id}")
+ #myshixuns = shixun.myshixuns.where(:user_id => student_works_user_id).includes(games: [:outputs])
+
+ # 最大评测次数
+ max_evaluate_count = 0
+
+ # 数据库操作改成对hash数组的操作
+ objects =
+ myshixuns.map do |myshixun|
+ # 评测次数
+ evaluate_count = myshixun.games.inject(0) {|sum, g| sum + g.outputs.pluck(:query_index).first.to_i}
+ # 获取最大评测次数
+ max_evaluate_count = (evaluate_count > max_evaluate_count ? evaluate_count : max_evaluate_count)
+ # 通关耗时
+ pass_consume_time = (myshixun.games.where(status: 2).pluck(:cost_time).sum / 60.0)
+ # 总耗时
+ all_time = (myshixun.games.pluck(:cost_time).sum / 60.0)
+ # 通关得分
+ user_total_score = myshixun.total_score.to_i
+ # 耗时,保留2位小数,
+ consume_time = all_time <= 1 ? 1 : Math.log(all_time).to_f
+ consume_time = format("%.2f", consume_time).to_f
+ # 效率
+ efficiency = (pass_consume_time == 0 ? 0 : Math.log((user_total_score / pass_consume_time.to_f) + 1.0))
+ efficiency = format("%.2f", efficiency).to_f
+
+ {consume_time: consume_time, evaluate_count: evaluate_count, user_id: myshixun.user_id,
+ user_total_score: user_total_score, efficiency: efficiency}
+ end
+
+ # 我的效率
+ myself_eff = []
+ myself_object = []
+ # 最小效率值
+ min_efficiency = objects.map {|o| o[:efficiency]}.min
+ # 最大效率值
+ # max_efficiency = objects.map{|o| o[:efficiency]}.max
+ results =
+ objects.each_with_index.map do |object, index|
+ y_score = (object[:evaluate_count] == 0 ? 0 : (object[:user_total_score] / object[:evaluate_count].to_f))
+ eff_score = format("%.2f", y_score).to_f
+ # 效率值
+ efficiency = object[:efficiency] < 0 ?
+ format("%.2f", object[:efficiency] - min_efficiency) : object[:efficiency]
+
+ # 圆圈的大小
+ round_size = (object[:evaluate_count] == 0 ? 0 : Math.log(max_evaluate_count / object[:evaluate_count].to_f)) * 3.14
+ # 作品本人的数据
+ if object[:user_id] == work.user.id
+ myself_eff = [index + 1, efficiency]
+ myself_object = [object[:consume_time], eff_score, round_size]
+ end
+ {:eff_score => eff_score, :user_id => object[:user_id], :consume_time => object[:consume_time],
+ :efficiency => efficiency, :round_size => round_size}
+ end
+
+ # 对结果进行排序
+ efficiency_list =
+ results.sort {|x, y| x[:efficiency] <=> y[:efficiency]}.each_with_index.map do |result, index|
+ [index + 1, result[:efficiency], result[:user_id]]
+ end
+
+ consume_list =
+ results.sort {|x, y| x[:consume_time] <=> y[:consume_time]}.map do |result|
+ [result[:consume_time], result[:eff_score], result[:round_size], result[:user_id]]
+ end
+
+ return {myself_eff: myself_eff, myself_object: myself_object, efficiency_list: efficiency_list, consume_list: consume_list}
+ end
+
+ # tpi详情信息
+ def output_detail game, output
+ if game.challenge.st == 0
+ output.try(:out_put).delete("详情如下:")
+ else
+ if game.status == 2
+ "评测通过"
+ else
+ "共有#{game.challenge.challenge_chooses.count}组测试集,其中有#{game.challenge.challenge_chooses.count-game.choose_correct_num}组测试结果不匹配"
+ end
+ end
+ end
+
+ def allow_score homework, identity, user_id, work
+ (identity < Course::STUDENT && !work.ultimate_score) ||
+ (identity == Course::STUDENT && homework.anonymous_comment &&
+ [3, 4].include?(homework.homework_detail_manual.comment_status) &&
+ StudentWorksEvaluationDistribution.where(user_id: user_id, student_work_id: work.id).count > 0)
+ end
+
+ def message_user_name(message_user, score, work_user, current_user, identity)
+ user_name = ""
+ user_login = "--"
+ image_url = "--"
+ if score.reviewer_role == 3 && score.user == message_user && message_user != current_user && identity >= Course::STUDENT
+ user_name = "匿评人"
+ elsif score.reviewer_role == 3 && work_user == message_user && message_user != current_user && identity >= Course::STUDENT
+ user_name = "被匿评人"
+ else
+ user_name = message_user.real_name
+ user_login = message_user.login
+ image_url = url_to_avatar(score.user)
+ end
+ {user_name: user_name, user_login: user_login, user_image_url: image_url}
+ end
+
+ def appeal_info work_appeal, user_identity, current_user
+ if user_identity < Course::STUDENT || work_appeal.user == current_user
+ user_name = work_appeal.user.try(:real_name)
+ user_login = work_appeal.user.try(:login)
+ image_url = url_to_avatar(work_appeal.user)
+ else
+ user_name = "匿名"
+ user_login = "--"
+ image_url = "--"
+ end
+ {id: work_appeal.id, user_name: user_name, user_login: user_login, user_image_url: image_url, time: work_appeal.created_at, content: work_appeal.comment}
+ end
+end
diff --git a/app/helpers/subjects_helper.rb b/app/helpers/subjects_helper.rb
new file mode 100644
index 000000000..21fb3bb0d
--- /dev/null
+++ b/app/helpers/subjects_helper.rb
@@ -0,0 +1,13 @@
+module SubjectsHelper
+
+ # 实训路径的发布状态
+ def publish_status subject, is_manager, user
+ status = -1
+ if is_manager
+ status = 0 if subject.status == 0 && subject.shixuns_count > 0
+ status = 1 if subject.status == 1
+ status = 2 if subject.status == 2 && user.admin?
+ end
+ status
+ end
+end
diff --git a/app/helpers/tem_tests_helper.rb b/app/helpers/tem_tests_helper.rb
new file mode 100644
index 000000000..15bfcc4b3
--- /dev/null
+++ b/app/helpers/tem_tests_helper.rb
@@ -0,0 +1,2 @@
+module TemTestsHelper
+end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
new file mode 100644
index 000000000..bee0f6b1a
--- /dev/null
+++ b/app/helpers/users_helper.rb
@@ -0,0 +1,2 @@
+module UsersHelper
+end
diff --git a/app/imports/application_import.rb b/app/imports/application_import.rb
new file mode 100644
index 000000000..cd2e8734c
--- /dev/null
+++ b/app/imports/application_import.rb
@@ -0,0 +1,5 @@
+class ApplicationImport
+ def logger(msg)
+ Rails.logger.error(msg)
+ end
+end
\ No newline at end of file
diff --git a/app/imports/base_import_excel.rb b/app/imports/base_import_excel.rb
new file mode 100644
index 000000000..9646c8ec0
--- /dev/null
+++ b/app/imports/base_import_excel.rb
@@ -0,0 +1,29 @@
+class BaseImportExcel < ApplicationImport
+ Error = Class.new(StandardError)
+
+ attr_reader :sheet
+
+ def initialize(path)
+ raise Error, '不支持的文件格式' unless path.end_with?('.xls') || path.end_with?('.xlsx')
+
+ begin
+ @sheet = Roo::Spreadsheet.open(path).sheet(0)
+ rescue Exception => ex
+ logger ex.message
+ ex.backtrace.each(&method(:logger))
+ raise Error, '打开文件失败'
+ end
+
+ check_sheet_valid!
+ end
+
+ def read_each(&block);end
+
+ private
+
+ def check_sheet_valid!;end
+
+ def raise_import_error(message)
+ raise Error, message
+ end
+end
\ No newline at end of file
diff --git a/app/imports/ecs/import_achievement_excel.rb b/app/imports/ecs/import_achievement_excel.rb
new file mode 100644
index 000000000..1aa703bc4
--- /dev/null
+++ b/app/imports/ecs/import_achievement_excel.rb
@@ -0,0 +1,37 @@
+class Ecs::ImportAchievementExcel < BaseImportExcel
+ def average_score_template?
+ type == :average_score
+ end
+
+ def detail_score_template?
+ type == :detail_score
+ end
+
+ def read_each(&block)
+ average_score_template? ? read_average_score(&block) : read_detail_score(&block)
+ end
+
+ private
+
+ def read_average_score(&block)
+ block.call(sheet.row(3))
+ end
+
+ def read_detail_score(&block)
+ 3.upto(sheet.last_row) do |index|
+ data = sheet.row(index)
+ next if data.all?(:blank?)
+
+ block.call(data)
+ end
+ end
+
+ def type
+ @_type ||= sheet.cell(1, 1)&.strip == '学号' && sheet.cell(1, 2)&.strip == '姓名' ? :detail_score : :average_score
+ end
+
+ def check_sheet_valid!
+ raise_import_error('请按照模板格式导入') if sheet.last_row.nil? || sheet.last_row < 3
+ raise_import_error('平均成绩只能有一行数据') if average_score_template? && sheet.last_row > 3
+ end
+end
\ No newline at end of file
diff --git a/app/imports/ecs/import_course_excel.rb b/app/imports/ecs/import_course_excel.rb
new file mode 100644
index 000000000..5cfedca69
--- /dev/null
+++ b/app/imports/ecs/import_course_excel.rb
@@ -0,0 +1,16 @@
+class Ecs::ImportCourseExcel < BaseImportExcel
+ def read_each(&block)
+ 2.upto(sheet.last_row) do |row_num|
+ name = sheet.row(row_num)[0].to_s.strip
+ next if name.blank?
+
+ block.call(name)
+ end
+ end
+
+ private
+
+ def check_sheet_valid!
+ raise_import_error('请按照模板格式导入') if sheet.cell(1, 1) != '课程名称(必填)'
+ end
+end
\ No newline at end of file
diff --git a/app/imports/ecs/import_student_excel.rb b/app/imports/ecs/import_student_excel.rb
new file mode 100644
index 000000000..ad60a5e1d
--- /dev/null
+++ b/app/imports/ecs/import_student_excel.rb
@@ -0,0 +1,19 @@
+class Ecs::ImportStudentExcel < BaseImportExcel
+ def read_each(&block)
+ 2.upto(sheet.last_row) do |row_num|
+ data = sheet.row(row_num)
+
+ number = data[0].to_s.strip
+ name = data[1].to_s.strip
+ next if number.blank?
+
+ block.call(number, name)
+ end
+ end
+
+ private
+
+ def check_sheet_valid!
+ raise_import_error('请按照模板格式导入') if sheet.cell(1, 1) != '学号(必填)' || sheet.cell(1, 2) != '姓名'
+ end
+end
\ No newline at end of file
diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb
new file mode 100644
index 000000000..d7c48b9a3
--- /dev/null
+++ b/app/jobs/application_job.rb
@@ -0,0 +1,2 @@
+class ApplicationJob < ActiveJob::Base
+end
diff --git a/app/jobs/apply_teacher_role_join_course_notify_job.rb b/app/jobs/apply_teacher_role_join_course_notify_job.rb
new file mode 100644
index 000000000..c2495c036
--- /dev/null
+++ b/app/jobs/apply_teacher_role_join_course_notify_job.rb
@@ -0,0 +1,23 @@
+# 申请成为老师加入课堂 消息通知
+class ApplyTeacherRoleJoinCourseNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(user_id, course_id, role)
+ user = User.find_by(id: user_id)
+ course = Course.find_by(id: course_id)
+ return if user.blank? || course.blank?
+
+ attrs = %i[user_id trigger_user_id container_id container_type belong_container_id
+ belong_container_type tiding_type extra created_at updated_at]
+
+ same_attrs = {
+ trigger_user_id: user.id, container_id: course.id, container_type: 'JoinCourse',
+ belong_container_id: course.id, belong_container_type: 'Course', tiding_type: 'Apply', extra: role
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ course.teachers_without_assistant_professor.each do |teacher|
+ worker.add same_attrs.merge(user_id: teacher.user_id)
+ end
+ end
+ end
+end
diff --git a/app/jobs/exercise_publish_notify_job.rb b/app/jobs/exercise_publish_notify_job.rb
new file mode 100644
index 000000000..c66fa5660
--- /dev/null
+++ b/app/jobs/exercise_publish_notify_job.rb
@@ -0,0 +1,34 @@
+# 试卷发布 消息通知
+class ExercisePublishNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(exercise_id, receiver_ids)
+ exercise = Exercise.find_by(id: exercise_id)
+ return if exercise.blank?
+ user = exercise.user
+
+ attrs = %i[
+ user_id trigger_user_id container_id container_type parent_container_id parent_container_type
+ belong_container_id belong_container_type viewed tiding_type created_at updated_at
+ ]
+
+ same_attrs = {
+ trigger_user_id: user.id, container_id: exercise.id, container_type: 'Exercise',
+ parent_container_id: exercise.id, parent_container_type: 'ExercisePublish',
+ belong_container_id: exercise.course_id, belong_container_type: 'Course',
+ viewed: 0, tiding_type: 'Exercise'
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ teacher_ids = exercise.course.teachers.pluck(:user_id)
+ unless exercise.tidings.exists?(parent_container_type: 'ExercisePublish', user_id: teacher_ids)
+ exercise.course.teachers.find_each do |teacher|
+ worker.add same_attrs.merge(user_id: teacher.user_id)
+ end
+ end
+
+ receiver_ids.each do |user_id|
+ worker.add same_attrs.merge(user_id: user_id)
+ end
+ end
+ end
+end
diff --git a/app/jobs/graduation_task_publish_notify_job.rb b/app/jobs/graduation_task_publish_notify_job.rb
new file mode 100644
index 000000000..3489aa711
--- /dev/null
+++ b/app/jobs/graduation_task_publish_notify_job.rb
@@ -0,0 +1,26 @@
+# 任务发布 消息通知
+class GraduationTaskPublishNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(graduation_task_id)
+ task = GraduationTask.find_by(id: graduation_task_id)
+ return if task.blank?
+
+ attrs = %i[
+ user_id trigger_user_id container_id container_type parent_container_id parent_container_type
+ belong_container_id belong_container_type viewed tiding_type created_at updated_at
+ ]
+
+ same_attrs = {
+ trigger_user_id: task.user_id, container_id: task.id, container_type: 'GraduationTask',
+ parent_container_id: task.id, parent_container_type: 'TaskPublish',
+ belong_container_id: task.course_id, belong_container_type: 'Course',
+ viewed: 0, tiding_type: 'GraduationTask'
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ task.course.students.find_each do |student|
+ worker.add same_attrs.merge(user_id: student.user_id)
+ end
+ end
+ end
+end
diff --git a/app/jobs/homework_common_push_notify_job.rb b/app/jobs/homework_common_push_notify_job.rb
new file mode 100644
index 000000000..961face58
--- /dev/null
+++ b/app/jobs/homework_common_push_notify_job.rb
@@ -0,0 +1,43 @@
+# 任务发布 消息通知
+class HomeworkCommonPushNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(homework_common_id, group_ids)
+ homework = HomeworkCommon.find_by(id: homework_common_id)
+ return if homework.blank?
+ course = homework.course
+
+ if group_ids.present?
+ students = course.students.where(course_group_id: group_ids)
+ subquery = course.teacher_course_groups.where(course_group_id: group_ids).select(:course_member_id)
+ teachers = course.teachers.where(id: subquery)
+ else
+ students = course.students
+ teachers = course.teachers
+ end
+
+ attrs = %i[
+ user_id trigger_user_id container_id container_type parent_container_id parent_container_type
+ belong_container_id belong_container_type viewed tiding_type created_at updated_at
+ ]
+
+ same_attrs = {
+ trigger_user_id: homework.user_id, container_id: homework.id, container_type: 'HomeworkCommon',
+ parent_container_id: homework.id, parent_container_type: 'HomeworkPublish',
+ belong_container_id: task.course_id, belong_container_type: 'Course',
+ viewed: 0, tiding_type: 'HomeworkCommon'
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ teacher_ids = teachers.pluck(:user_id)
+ if homework.tidings.where(parent_container_type: 'HomeworkPublish', user_id: teacher_ids).exists?
+ teacher_ids.each do |user_id|
+ worker.add same_attrs.merge(user_id: user_id)
+ end
+ end
+
+ students.pluck(:user_id).each do |user_id|
+ worker.add same_attrs.merge(user_id: user_id)
+ end
+ end
+ end
+end
diff --git a/app/jobs/homework_publish_update_work_status_job.rb b/app/jobs/homework_publish_update_work_status_job.rb
new file mode 100644
index 000000000..0060ab270
--- /dev/null
+++ b/app/jobs/homework_publish_update_work_status_job.rb
@@ -0,0 +1,33 @@
+class HomeworkPublishUpdateWorkStatusJob < ApplicationJob
+ # 作业发布时更新学生(发布前已开启过实训)的作业状态和成绩
+ queue_as :default
+
+ def perform(group_ids, homework_id)
+ # Do something later
+ homework = HomeworkCommon.find_by(id: homework_id)
+ return if homework.blank?
+ course = homework.course
+ return if course.blank?
+
+ where_con = group_ids.nil? ? {course_id: homework.course_id, role: 4} : {course_id: homework.course_id, role: 4, course_group_id: group_ids}
+
+ shixun_id = homework.homework_commons_shixun.try(:shixun_id)
+
+ student_works = homework.student_works.joins("join course_members on student_works.user_id = course_members.user_id").
+ where(course_members: where_con, work_status: 0).joins("join myshixuns on myshixuns.user_id = student_works.user_id").
+ where(myshixuns: {shixun_id: shixun_id})
+
+ student_works.each do |work|
+ myshixun = Myshixun.find_by(shixun_id: shixun_id, user_id: work.user_id)
+
+ work.work_status = 1
+ work.commit_time = myshixun.updated_at
+ work.update_time = myshixun.updated_at
+ work.myshixun_id = myshixun.id
+
+ work.save!
+
+ HomeworksService.new.set_shixun_final_score work
+ end
+ end
+end
diff --git a/app/jobs/poll_publish_notify_job.rb b/app/jobs/poll_publish_notify_job.rb
new file mode 100644
index 000000000..bc31957e0
--- /dev/null
+++ b/app/jobs/poll_publish_notify_job.rb
@@ -0,0 +1,34 @@
+# 问卷发布 消息通知
+class PollPublishNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(poll_id, receiver_ids)
+ poll = Poll.find_by(id: poll_id)
+ return if poll.blank?
+ user = poll.user
+
+ attrs = %i[
+ user_id trigger_user_id container_id container_type parent_container_id parent_container_type
+ belong_container_id belong_container_type viewed tiding_type created_at updated_at
+ ]
+
+ same_attrs = {
+ trigger_user_id: user.id, container_id: poll.id, container_type: 'Poll',
+ parent_container_id: poll.id, parent_container_type: 'PollPublish',
+ belong_container_id: poll.course_id, belong_container_type: 'Course',
+ viewed: 0, tiding_type: 'Poll'
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ teacher_ids = poll.course.teachers.pluck(:user_id)
+ unless poll.tidings.exists?(parent_container_type: 'PollPublish', user_id: teacher_ids)
+ poll.course.teachers.find_each do |teacher|
+ worker.add same_attrs.merge(user_id: teacher.user_id)
+ end
+ end
+
+ receiver_ids.each do |user_id|
+ worker.add same_attrs.merge(user_id: user_id)
+ end
+ end
+ end
+end
diff --git a/app/jobs/student_join_course_notify_job.rb b/app/jobs/student_join_course_notify_job.rb
new file mode 100644
index 000000000..3768ee756
--- /dev/null
+++ b/app/jobs/student_join_course_notify_job.rb
@@ -0,0 +1,23 @@
+# 学生加入课堂 消息通知
+class StudentJoinCourseNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(student_id, course_id)
+ student = User.find_by(id: student_id)
+ course = Course.find_by(id: course_id)
+ return if student.blank? || course.blank?
+
+ attrs = %i[user_id trigger_user_id status container_id container_type belong_container_id
+ belong_container_type tiding_type created_at updated_at]
+
+ same_attrs = {
+ trigger_user_id: student.id, container_id: course.id, container_type: 'StudentJoinCourse',
+ belong_container_id: course.id, belong_container_type: 'Course', tiding_type: 'System', status: 0
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ course.teachers_without_assistant_professor.each do |teacher|
+ worker.add same_attrs.merge(user_id: teacher.user_id)
+ end
+ end
+ end
+end
diff --git a/app/jobs/student_work_score_appeal_notify_job.rb b/app/jobs/student_work_score_appeal_notify_job.rb
new file mode 100644
index 000000000..4d8076a0e
--- /dev/null
+++ b/app/jobs/student_work_score_appeal_notify_job.rb
@@ -0,0 +1,29 @@
+# 学生匿评进行申诉 消息通知
+class StudentWorkScoreAppealNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(course_id, score_appeal_id, user_id)
+ course = Course.find_by(id: course_id)
+ appeal = StudentWorksScoresAppeal.find_by(id: score_appeal_id)
+ user = User.find_by(id: user_id)
+ return if [course, appeal, user].any?(&:blank?)
+ score = appeal.student_works_score
+
+ attrs = %i[user_id trigger_user_id container_id container_type parent_container_id parent_container_type
+ belong_container_id belong_container_type tiding_type viewed status created_at updated_at]
+
+ same_attrs = {
+ trigger_user_id: user.id,
+ container_id: appeal.id, container_type: 'StudentWorksScoresAppeal',
+ parent_container_id: score.student_work_id, parent_container_type: 'StudentWork',
+ belong_container_id: course.id, belong_container_type: 'Course',
+ tiding_type: 'HomeworkCommon', viewed: 0, status: 0
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ course.course_member(user).member_teachers.each do |teacher|
+ worker.add same_attrs.merge(user_id: teacher.user_id)
+ end
+ worker.add same_attrs.merge(user_id: score.user_id)
+ end
+ end
+end
diff --git a/app/jobs/submit_graduation_work_notify_job.rb b/app/jobs/submit_graduation_work_notify_job.rb
new file mode 100644
index 000000000..17cfc21d2
--- /dev/null
+++ b/app/jobs/submit_graduation_work_notify_job.rb
@@ -0,0 +1,32 @@
+# 学生提交作品 消息通知
+class SubmitGraduationWorkNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(task_id, student_ids)
+ task = GraduationTask.find_by(id: task_id)
+ return if task.blank? || student_ids.blank?
+ course = task.course
+
+ attrs = %i[user_id trigger_user_id container_id container_type parent_container_id parent_container_type
+ belong_container_id belong_container_type tiding_type viewed created_at updated_at]
+
+ same_attrs = {
+ container_type: 'GraduationWork', parent_container_id: task.id, parent_container_type: 'GraduationTask',
+ belong_container_id: course.id, belong_container_type: 'Course', tiding_type: 'GraduationTask', viewed: 0
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ student_ids.each do |user_id|
+ next unless User.exists?(id: user_id)
+
+ work = task.graduation_works.find_by(user_id: user_id)
+ next if work.blank?
+
+ attrs = same_attrs.merge(trigger_user_id: user_id, container_id: work.id)
+
+ course.course_member(user_id).member_teachers.find_each do |teacher|
+ worker.add attrs.merge(user_id: teacher.user_id)
+ end
+ end
+ end
+ end
+end
diff --git a/app/jobs/submit_student_work_notify_job.rb b/app/jobs/submit_student_work_notify_job.rb
new file mode 100644
index 000000000..acb7873ac
--- /dev/null
+++ b/app/jobs/submit_student_work_notify_job.rb
@@ -0,0 +1,32 @@
+# 学生提交作业 消息通知
+class SubmitStudentWorkNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(homework_id, student_ids)
+ homework = HomeworkCommon.find_by(id: homework_id)
+ return if homework.blank? || student_ids.blank?
+ course = homework.course
+
+ attrs = %i[user_id trigger_user_id container_id container_type parent_container_id parent_container_type
+ belong_container_id belong_container_type tiding_type viewed created_at updated_at]
+
+ same_attrs = {
+ container_type: 'StudentWork', parent_container_id: homework.id, parent_container_type: 'HomeworkCommon',
+ belong_container_id: course.id, belong_container_type: 'Course', tiding_type: 'HomeworkCommon', viewed: 0
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ student_ids.each do |user_id|
+ next unless User.exists?(id: user_id)
+
+ work = homework.student_works.find_by(user_id: user_id)
+ next if work.blank?
+
+ attrs = same_attrs.merge(trigger_user_id: user_id, container_id: work.id)
+
+ course.course_member(user_id).member_teachers.find_each do |teacher|
+ worker.add attrs.merge(user_id: teacher.user_id)
+ end
+ end
+ end
+ end
+end
diff --git a/app/jobs/teacher_invite_join_course_notify_job.rb b/app/jobs/teacher_invite_join_course_notify_job.rb
new file mode 100644
index 000000000..5f9812bb4
--- /dev/null
+++ b/app/jobs/teacher_invite_join_course_notify_job.rb
@@ -0,0 +1,23 @@
+# 老师邀请用户加入课堂 消息通知
+class TeacherInviteJoinCourseNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(inviter_id, course_id, role, user_ids)
+ inviter = User.find_by(id: inviter_id)
+ course = Course.find_by(id: course_id)
+ return if inviter.blank? || course.blank?
+
+ attrs = %i[user_id trigger_user_id container_id container_type belong_container_id
+ belong_container_type tiding_type extra created_at updated_at]
+
+ same_attrs = {
+ trigger_user_id: inviter.id, container_id: course.id, container_type: 'TeacherJoinCourse',
+ belong_container_id: course.id, belong_container_type: 'Course', tiding_type: 'System', extra: role
+ }
+ Tiding.bulk_insert(*attrs) do |worker|
+ user_ids.each do |user_id|
+ worker.add same_attrs.merge(user_id: user_id)
+ end
+ end
+ end
+end
diff --git a/app/libs/base64_image_converter.rb b/app/libs/base64_image_converter.rb
new file mode 100644
index 000000000..2a8155283
--- /dev/null
+++ b/app/libs/base64_image_converter.rb
@@ -0,0 +1,77 @@
+class Base64ImageConverter
+ BASE64_HEAD = 'data:image/jpeg;base64,'.freeze
+
+ Error = Class.new(StandardError)
+ OutLimit = Class.new(Error)
+ InvalidData = Class.new(Error)
+ InvalidFormat = Class.new(Error)
+
+ attr_reader :opts
+
+ def initialize(**opts)
+ @opts = opts
+ end
+
+ def convert(data)
+ raise InvalidData, '不合法的Base64数据' unless valid_base64?(data)
+
+ io = StringIO.new(Base64.decode64(image_data data))
+
+ raise OutLimit, '文件大小超过限制' if opts[:max_size].present? && io.size > opts[:max_size]
+
+ raise InvalidFormat, '无效的格式' unless Image.new(io).image?
+
+ io
+ end
+
+ private
+
+ def valid_base64?(data)
+ data&.start_with?(BASE64_HEAD)
+ end
+
+ def image_data(data)
+ data[BASE64_HEAD.size..-1]
+ end
+
+ def size_limit
+ EduSetting.find_by_name('upload_avatar_max_size')&.value
+ end
+
+ class Image
+ attr_reader :io
+
+ def initialize(io)
+ raise ArgumentError unless io.respond_to?(:read)
+ @io = io
+ end
+
+ def data
+ @_data ||= begin
+ data = io.read(9)
+ io.rewind
+ data
+ end
+ end
+
+ def image?
+ bitmap? || gif? || jpeg? || png?
+ end
+
+ def bitmap?
+ data[0,2] == 66.chr + 77.chr
+ end
+
+ def gif?
+ data[0,4] == 71.chr + 73.chr + 70.chr + 56.chr
+ end
+
+ def jpeg?
+ data[0,3] == 0xff.chr + 0xd8.chr + 0xff.chr
+ end
+
+ def png?
+ data[0,2] == 0x89.chr + 80.chr
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/libs/custom_regexp.rb b/app/libs/custom_regexp.rb
new file mode 100644
index 000000000..d7afc61ed
--- /dev/null
+++ b/app/libs/custom_regexp.rb
@@ -0,0 +1,4 @@
+module CustomRegexp
+ PHONE = /1\d{10}/
+ EMAIL = /^[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/
+end
\ No newline at end of file
diff --git a/app/libs/custom_sortable.rb b/app/libs/custom_sortable.rb
new file mode 100644
index 000000000..21ead8a4e
--- /dev/null
+++ b/app/libs/custom_sortable.rb
@@ -0,0 +1,38 @@
+module CustomSortable
+ extend ActiveSupport::Concern
+
+ included do |base|
+ base.instance_variable_set("@_sort_options", {})
+ base.instance_variable_set("@_sort_columns", [])
+ base.instance_variable_set("@_sort_directions", %w(asc desc))
+ end
+
+ def custom_sort(relations, sort_by, sort_direction)
+ sort_by = self.class.sort_options[:default_by] if sort_by.blank?
+ sort_direction = self.class.sort_options[:default_direction] if sort_direction.blank?
+
+ return relations unless self.class.check_sort_parameter_validate(sort_by.to_s, sort_direction.to_s)
+
+ order_method = self.class.sort_options[:reorder] ? :reorder : :order
+ relations.send(order_method, "#{sort_by} #{sort_direction}")
+ end
+
+ module ClassMethods
+ def sort_columns(*columns)
+ opts = columns.extract_options!
+ @_sort_options[:default_by] = opts[:default_by].to_s
+ @_sort_options[:default_direction] = opts[:default_direction].to_s
+ @_sort_options[:reorder] = opts[:reorder]
+
+ @_sort_columns = columns.map(&:to_s)
+ end
+
+ def check_sort_parameter_validate(sort_by, sort_direction)
+ (sort_by.blank? || @_sort_columns.include?(sort_by)) && @_sort_directions.include?(sort_direction)
+ end
+
+ def sort_options
+ @_sort_options
+ end
+ end
+end
diff --git a/app/libs/util.rb b/app/libs/util.rb
new file mode 100644
index 000000000..f6c8855b9
--- /dev/null
+++ b/app/libs/util.rb
@@ -0,0 +1,31 @@
+module Util
+ module_function
+
+ def days_between(time, other_time)
+ raise ArgumentError if time.blank? || other_time.blank?
+ Date.parse(time.to_s) - Date.parse(other_time.to_s)
+ end
+
+ def convert_base64_image(str, **opts)
+ return if str.blank?
+
+ Base64ImageConverter.new(**opts).convert(str)
+ end
+
+ def write_file(io, path)
+ dir = File.dirname(path)
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
+
+ Rails.logger.info("### save file #{path}, size: #{io.size} ~")
+ File.open(path, 'wb') do |file|
+ if io.respond_to?(:read)
+ io.rewind
+ while buffer = io.read(8192)
+ file.write(buffer)
+ end
+ else
+ file.write(io)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
new file mode 100644
index 000000000..8e1c7ec84
--- /dev/null
+++ b/app/mailers/application_mailer.rb
@@ -0,0 +1,4 @@
+class ApplicationMailer < ActionMailer::Base
+ default from: 'from@example.com'
+ layout 'mailer'
+end
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
new file mode 100644
index 000000000..03ef63bb2
--- /dev/null
+++ b/app/mailers/user_mailer.rb
@@ -0,0 +1,10 @@
+class UserMailer < ApplicationMailer
+ # 注意:这个地方一定要和你的邮箱服务域名一致
+ default from: 'educoder@trustie.org'
+
+ # 用户注册验证码
+ def register_email(mail, code)
+ @code = code
+ mail(to: mail, subject: '验证你的电子邮件')
+ end
+end
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
new file mode 100644
index 000000000..a876440c3
--- /dev/null
+++ b/app/models/application_record.rb
@@ -0,0 +1,9 @@
+class ApplicationRecord < ActiveRecord::Base
+ include NumberDisplayHelper
+
+ self.abstract_class = true
+
+ def format_time(time)
+ time.present? ? time.strftime('%Y-%m-%d %H:%M') : ''
+ end
+end
diff --git a/app/models/apply_action.rb b/app/models/apply_action.rb
new file mode 100644
index 000000000..2d31f394d
--- /dev/null
+++ b/app/models/apply_action.rb
@@ -0,0 +1,22 @@
+# 申请消息
+class ApplyAction < ApplicationRecord
+ has_many :tidings, :as => :container, :dependent => :destroy
+ after_create :send_tiding
+
+ def send_tiding
+ if container_type == 'TrialAuthorization' && status == 1
+ tidings.create(user_id: user_id, trigger_user_id: 0, status: 1, viewed: 0, tiding_type: 'System',
+ parent_container_id: container_id, parent_container_type: container_type,
+ belong_container_id: container_id, belong_container_type: 'User')
+ else
+ belong_container_type = if container_type == 'TrialAuthorization'
+ 'User'
+ else
+ container_type == 'ApplyShixun' ? 'Shixun' : 'Subject'
+ end
+ tidings.create(user_id: '1', trigger_user_id: user_id, status: 0, viewed: 0, tiding_type: 'Apply',
+ parent_container_id: container_id, parent_container_type: container_type,
+ belong_container_id: container_id, belong_container_type: belong_container_type)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/models/apply_user_authentication.rb b/app/models/apply_user_authentication.rb
new file mode 100644
index 000000000..3907c3f16
--- /dev/null
+++ b/app/models/apply_user_authentication.rb
@@ -0,0 +1,9 @@
+# status:0 审核中 1 同意 2 拒绝 3 撤销
+# auth_type:1 实名认证, 2 职业认证
+class ApplyUserAuthentication < ApplicationRecord
+ belongs_to :user
+
+ scope :real_name_auth, -> { where(auth_type: 1) }
+ scope :professional_auth, -> { where(auth_type: 2) }
+ scope :processing, -> { where(status: 0) }
+end
diff --git a/app/models/attachment.rb b/app/models/attachment.rb
new file mode 100644
index 000000000..bdd749108
--- /dev/null
+++ b/app/models/attachment.rb
@@ -0,0 +1,118 @@
+class Attachment < ApplicationRecord
+ include BaseModel
+ include Publicable
+ include Publishable
+ include Lockable
+
+ belongs_to :container, polymorphic: true, touch: true, optional: true
+ belongs_to :author, class_name: "User", foreign_key: :author_id
+ belongs_to :course, foreign_key: :container_id, optional: true
+ has_many :attachment_group_settings, :dependent => :destroy
+ has_many :attachment_histories, -> { order(version: :desc) }, :dependent => :destroy
+
+ scope :by_filename_or_user_name, -> (keywords) { joins(:author).where("filename like :search or LOWER(concat(users.lastname, users.firstname)) LIKE :search",
+ :search => "%#{keywords.split(" ").join('|')}%") unless keywords.blank? }
+ scope :by_keywords, -> (keywords) { where("filename LIKE ?", "%#{keywords.split(" ").join('|')}%") unless keywords.blank? }
+ scope :ordered, -> (opts = {}) { order("#{opts[:sort_type]} #{opts[:sort] == 1 ? 'asc': 'desc'}") }
+ scope :by_course_second_category_id, -> (course_second_category_id = 0) { where(course_second_category_id: course_second_category_id) }
+ scope :contains_only_course, -> { where(container_type: 'Course') }
+ scope :contains_only_project, -> { where(container_type: 'Project') }
+ scope :contains_course_and_project, -> { contains_only_course.or(contains_only_project) }
+ scope :mine, -> (author_id) { where(author_id: author_id) }
+ scope :simple_columns, -> { select(:id, :filename, :filesize, :created_on, :cloud_url, :author_id) }
+ scope :search_by_container, -> (ids) {where(container_id: ids)}
+
+ validates_length_of :description, maximum: 100
+
+ def diskfile
+ File.join(File.join(Rails.root, "files"), disk_directory.to_s, disk_filename.to_s)
+ end
+
+ def title
+ filename
+ end
+
+ def downloads_count
+ downloads
+ end
+
+ def quotes_count
+ quotes.nil? ? 0 : quotes
+ end
+
+ def self.associate_container(ids, container_id, container_type, attachtype=1)
+ return false if ids.blank? || !ids.is_a?(Array)
+ ids.each do |id|
+ attachment = Attachment.find id
+ attachment.update_attributes(container_id: container_id, container_type: container_type, attachtype: attachtype)
+ end
+ end
+
+ # Returns an unsaved copy of the attachment
+ def copy(attributes=nil)
+ copy = self.class.new
+ copy.attributes = self.attributes.dup.except("id", "downloads", "quotes")
+ copy.attributes = attributes if attributes
+ copy
+ end
+
+ def set_publish_time(publish_time)
+ self.unified_setting = 1
+ if publish_time.blank?
+ self.publish_time = Time.now
+ self.is_publish = 1
+ else
+ self.is_publish = publish_time.to_s > (format_time Time.now).to_s ? 0 : 1
+ self.publish_time = publish_time.to_s > (format_time Time.now).to_s ? publish_time : Time.now
+ end
+ end
+
+ def set_course_group_publish_time(course, course_group_publish_times)
+ self.unified_setting = 0
+ min_publish_time = ""
+ course_group_publish_times.each do |obj|
+ if obj && obj[:course_group_id]
+ publish_time = obj[:publish_time]
+ if !publish_time.blank? && publish_time < min_publish_time
+ min_publish_time = publish_time
+ elsif publish_time.blank?
+ publish_time = Time.now
+ end
+ attachment_group_setting = self.attachment_group_settings.where(course_group_id: obj[:course_group_id], course_id: course.id).first
+ if attachment_group_setting.present?
+ attachment_group_setting.update_columns(publish_time: publish_time)
+ else
+ self.attachment_group_settings.create(
+ :course_group_id => obj[:course_group_id],
+ :course_id => course.id,
+ :publish_time => publish_time
+ )
+ end
+ end
+ end
+ self.is_publish = min_publish_time > (format_time Time.now).to_s ? 0 : 1
+ self.publish_time = min_publish_time > (format_time Time.now).to_s ? min_publish_time : self.created_on
+ end
+
+ def become_history
+ history = self.attachment_histories.first
+ new_attachment_history = AttachmentHistory.new(self.attributes.except("id", "resource_bank_id", "unified_setting", "course_second_category_id").merge(
+ attachment_id: self.id,
+ version: history.nil? ? 1 : history.version + 1,
+ ))
+ new_attachment_history
+ end
+
+ def copy_attributes_from_new_attachment(new_attachment)
+ self.attributes = new_attachment.attributes.dup.except("id","container_id","container_type","is_public","downloads", "quotes",'is_publish','publish_time')
+ end
+
+ def set_public(is_public)
+ if is_public == true
+ is_public = 1
+ elsif is_public == false
+ is_public = 0
+ end
+ end
+
+end
diff --git a/app/models/attachment_group_setting.rb b/app/models/attachment_group_setting.rb
new file mode 100644
index 000000000..67240d88f
--- /dev/null
+++ b/app/models/attachment_group_setting.rb
@@ -0,0 +1,6 @@
+class AttachmentGroupSetting < ActiveRecord::Base
+ belongs_to :attachment
+ belongs_to :course_group
+ belongs_to :course
+
+end
diff --git a/app/models/attachment_history.rb b/app/models/attachment_history.rb
new file mode 100644
index 000000000..220f92535
--- /dev/null
+++ b/app/models/attachment_history.rb
@@ -0,0 +1,23 @@
+class AttachmentHistory < ApplicationRecord
+ include Publishable
+ include Publicable
+
+ belongs_to :attachment, foreign_key: 'attachment_id'
+
+ def title
+ filename
+ end
+
+ def downloads_count
+ downloads
+ end
+
+ def quotes_count
+ quotes.nil? ? 0 : quotes
+ end
+
+ def public?
+ is_public == 1
+ end
+
+end
diff --git a/app/models/attendance.rb b/app/models/attendance.rb
new file mode 100644
index 000000000..ffe3f63a1
--- /dev/null
+++ b/app/models/attendance.rb
@@ -0,0 +1,16 @@
+class Attendance < ApplicationRecord
+ belongs_to :user
+
+ default_scope { order(created_at: :desc) }
+
+ def next_gold
+ # 超过1天即没有连续的签到则又从10个金币开始累加
+ return 50 if Util.days_between(Time.zone.now, created_at) > 1
+
+ [[score.to_i, 50].max + 10, 100].min
+ end
+
+ def today?
+ Util.days_between(Time.current, created_at).zero?
+ end
+end
diff --git a/app/models/board.rb b/app/models/board.rb
new file mode 100644
index 000000000..9f3152727
--- /dev/null
+++ b/app/models/board.rb
@@ -0,0 +1,25 @@
+class Board < ApplicationRecord
+ belongs_to :course, touch: true
+
+ has_many :messages, -> { order(created_on: :desc) }, dependent: :destroy
+ has_many :visible_messages, -> { visible }, class_name: "Message"
+ belongs_to :last_message, class_name: 'Message', foreign_key: :last_message_id, optional: true
+
+ scope :roots, -> { where(parent_id: 0)}
+
+ validates :name, presence: true, length: {maximum: 30}
+ # validates :description, presence: true, length: {maximum: 255}
+
+ # 子讨论区
+ def children
+ Board.where(parent_id: self.id).reorder("position asc")
+ end
+
+ def fix_name
+ return name if parent_id != 0
+
+ course_modules = self.course.board_course_modules
+ course_modules.present? ? course_modules[0].module_name : self.name
+ end
+
+end
diff --git a/app/models/career.rb b/app/models/career.rb
new file mode 100644
index 000000000..b7b6f11a1
--- /dev/null
+++ b/app/models/career.rb
@@ -0,0 +1,2 @@
+class Career < ApplicationRecord
+end
diff --git a/app/models/challenge.rb b/app/models/challenge.rb
new file mode 100644
index 000000000..a5d9874ce
--- /dev/null
+++ b/app/models/challenge.rb
@@ -0,0 +1,102 @@
+class Challenge < ApplicationRecord
+ # difficulty: 关卡难度: 1.简单 2.中等 3.困难
+ default_scope { order("challenges.position asc") }
+
+ belongs_to :shixun, :touch => true, counter_cache: true
+ belongs_to :user
+ has_many :challenge_samples, :dependent => :destroy
+ has_many :test_sets, :dependent => :destroy
+ has_many :challenge_tags, :dependent => :destroy
+ has_many :games, :dependent => :destroy
+ has_many :challenge_chooses, :dependent => :destroy
+ has_many :homework_challenge_settings, :dependent => :destroy
+ has_many :praise_tread, as: :praise_tread_object, dependent: :destroy
+ has_one :praise_tread_cache, as: :object, dependent: :destroy
+ has_many :tidings
+ # 参考答案
+ has_many :challenge_answers, :dependent => :destroy
+ has_many :exercise_bank_shixun_challenges, :dependent => :destroy
+
+ # acts_as_attachable
+
+ scope :base_attrs, -> { select([:id, :subject, :position, :shixun_id, :st, :score, :path, :task_pass, :modify_time,
+ :web_route, :answer]) }
+ scope :choose_type, -> { where(st: 1) }
+ scope :practice_type, -> { where(st: 0) }
+
+ scope :fields_for_list, -> { select([:id, :subject, :st, :score, :position, :shixun_id]) }
+
+ def next_challenge
+ position = self.position + 1
+ Challenge.where(:position => position, :shixun_id => self.shixun).first
+ end
+
+ # 用户关卡是否通关
+ def has_passed?(user_id)
+ self.games.present? && self.games.select{|game| game.user_id == user_id && game.status == 2}.length > 0
+ end
+
+ ## 选择题总分
+ def choose_score
+ self.challenge_chooses.sum(:score)
+ end
+
+ # 关卡总分
+ def all_score
+ if self.st == 1
+ self.choose_score
+ else
+ self.score
+ end
+ end
+
+ # 开启挑战
+ def open_game(user_id)
+ game = self.games.select([:status, :identifier]).where(user_id: user_id).first
+ shixun = self.shixun
+ if game.present?
+ shixun.task_pass || game.status != 3 ? "/tasks/#{game.identifier}" : ""
+ else
+ "/api/shixuns/#{shixun.identifier}/shixun_exec"
+ end
+ end
+
+ ## 用户关卡状态 0: 不能开启实训; 1:直接开启; 2表示已完成
+ def user_tpi_status user_id
+ # todo: 以前没加索引导致相同关卡,同一用户有多个games
+ game = self.games.where(user_id: user_id).last
+ status =
+ if game.blank?
+ self.position == 1 ? 1 : 0
+ elsif game.status == 2
+ 2
+ else
+ 1
+ end
+ end
+
+ ## 选择题答案
+ def choose_answer
+ result = []
+ self.challenge_chooses.each do |choose|
+ result << {:position => choose.position, :answer => (choose.answer.blank? ? choose.standard_answer : choose.answer)}
+ end
+ end
+
+ # 关卡用户通关数
+ def user_passed_count
+ games.where(status: 2).count
+ end
+
+ # 关卡用户正在挑战的人数
+ def playing_count
+ games.where(status: [0, 1]).count
+ end
+
+ def last_challenge
+ Challenge.find_by(position: position - 1, shixun_id: shixun_id)
+ end
+
+ # 关卡评测文件
+
+end
diff --git a/app/models/challenge_answer.rb b/app/models/challenge_answer.rb
new file mode 100644
index 000000000..93b11be66
--- /dev/null
+++ b/app/models/challenge_answer.rb
@@ -0,0 +1,4 @@
+class ChallengeAnswer < ApplicationRecord
+ default_scope { order("challenge_answers.level asc") }
+ belongs_to :challenge
+end
diff --git a/app/models/challenge_choose.rb b/app/models/challenge_choose.rb
new file mode 100644
index 000000000..07b0813bb
--- /dev/null
+++ b/app/models/challenge_choose.rb
@@ -0,0 +1,6 @@
+class ChallengeChoose < ApplicationRecord
+ default_scope {order("position asc")}
+ belongs_to :challenge, optional: true
+ has_many :challenge_tags, :dependent => :destroy
+ has_many :challenge_questions, dependent: :destroy
+end
diff --git a/app/models/challenge_question.rb b/app/models/challenge_question.rb
new file mode 100644
index 000000000..b0927aec0
--- /dev/null
+++ b/app/models/challenge_question.rb
@@ -0,0 +1,3 @@
+class ChallengeQuestion < ApplicationRecord
+ belongs_to :challenge_choose
+end
diff --git a/app/models/challenge_sample.rb b/app/models/challenge_sample.rb
new file mode 100644
index 000000000..e523a8087
--- /dev/null
+++ b/app/models/challenge_sample.rb
@@ -0,0 +1,4 @@
+class ChallengeSample < ApplicationRecord
+ belongs_to :challenge
+end
+
diff --git a/app/models/challenge_tag.rb b/app/models/challenge_tag.rb
new file mode 100644
index 000000000..6f176cc67
--- /dev/null
+++ b/app/models/challenge_tag.rb
@@ -0,0 +1,5 @@
+class ChallengeTag < ApplicationRecord
+
+ belongs_to :challenge, counter_cache: true
+ belongs_to :challenge_choose
+end
diff --git a/app/models/challenge_work_score.rb b/app/models/challenge_work_score.rb
new file mode 100644
index 000000000..8316e228b
--- /dev/null
+++ b/app/models/challenge_work_score.rb
@@ -0,0 +1,5 @@
+class ChallengeWorkScore < ApplicationRecord
+ belongs_to :user
+ belongs_to :student_work
+ belongs_to :challenge
+end
diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/app/models/concerns/base_model.rb b/app/models/concerns/base_model.rb
new file mode 100755
index 000000000..1f55d4db5
--- /dev/null
+++ b/app/models/concerns/base_model.rb
@@ -0,0 +1,34 @@
+module BaseModel
+ extend ActiveSupport::Concern
+
+ included do
+ scope :recent, -> { order(id: :desc) }
+ scope :exclude_ids, -> (ids) { where.not(id: ids.map(&:to_i)) }
+ scope :by_ids, -> (ids) { where(id: ids) unless ids.blank? }
+ scope :by_week, -> { where("created_at > ?", 7.days.ago.utc) }
+
+ delegate :url_helpers, to: 'Rails.application.routes'
+ end
+
+ # FIXME: 需要原子化操作
+ def push(hash)
+ hash.each_key do |key|
+ self.send("#{key}_will_change!")
+ old_val = self[key] || []
+ old_val << hash[key].to_i
+ old_val.uniq!
+ update_attributes(key => old_val)
+ end
+ end
+
+ # FIXME: 需要原子化操作
+ def pull(hash)
+ hash.each_key do |key|
+ self.send("#{key}_will_change!")
+ old_val = self[key]
+ return true if old_val.blank?
+ old_val.delete(hash[key].to_i)
+ update_attributes(key => old_val)
+ end
+ end
+end
diff --git a/app/models/concerns/lockable.rb b/app/models/concerns/lockable.rb
new file mode 100644
index 000000000..cb8072804
--- /dev/null
+++ b/app/models/concerns/lockable.rb
@@ -0,0 +1,10 @@
+module Lockable
+ extend ActiveSupport::Concern
+
+ included do
+ end
+
+ def locked?(is_member)
+ is_member == true ? false : !publiced?
+ end
+end
diff --git a/app/models/concerns/number_display_helper.rb b/app/models/concerns/number_display_helper.rb
new file mode 100644
index 000000000..0a0ee7638
--- /dev/null
+++ b/app/models/concerns/number_display_helper.rb
@@ -0,0 +1,13 @@
+module NumberDisplayHelper
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def number_display_methods(*columns, **opts)
+ columns.each do |column|
+ define_method "display_#{column}" do
+ number_to_currency(column.to_f, opts)
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/models/concerns/publicable.rb b/app/models/concerns/publicable.rb
new file mode 100644
index 000000000..a9c8cb41f
--- /dev/null
+++ b/app/models/concerns/publicable.rb
@@ -0,0 +1,9 @@
+module Publicable
+ extend ActiveSupport::Concern
+
+ included do
+ alias_attribute :public, :is_public
+ enum public: { publiced: 1, hidden: 0 }
+ end
+
+end
diff --git a/app/models/concerns/publishable.rb b/app/models/concerns/publishable.rb
new file mode 100644
index 000000000..6d7c1421f
--- /dev/null
+++ b/app/models/concerns/publishable.rb
@@ -0,0 +1,9 @@
+module Publishable
+ extend ActiveSupport::Concern
+
+ included do
+ alias_attribute :publish, :is_publish
+ enum publish: { published: 1, unpublish: 0 }
+ end
+
+end
diff --git a/app/models/concerns/watchable.rb b/app/models/concerns/watchable.rb
new file mode 100644
index 000000000..7be7d1b4c
--- /dev/null
+++ b/app/models/concerns/watchable.rb
@@ -0,0 +1,26 @@
+module Watchable
+ extend ActiveSupport::Concern
+
+ included do
+ has_many :watchers, as: :watchable, dependent: :destroy # 关注我的
+ has_many :watcher_users, through: :watchers, source: :user, validate: false # 我的粉丝
+
+ scope :watched_by, -> (user_id) { includes(:watchers).where(watchers: { user_id: user_id }) }
+ end
+
+ def watched?(watchable)
+ watchable.watchers.exists?(user: self)
+ end
+
+ def watch!(user)
+ user.watchers.create!(user: self)
+ end
+
+ def unwatch!(user)
+ obj = user.watchers.find_by(user: self)
+ obj.destroy! if obj.present?
+ end
+
+ module ClassMethods
+ end
+end
\ No newline at end of file
diff --git a/app/models/course.rb b/app/models/course.rb
new file mode 100644
index 000000000..dd2248d7c
--- /dev/null
+++ b/app/models/course.rb
@@ -0,0 +1,359 @@
+class Course < ApplicationRecord
+ has_many :boards, dependent: :destroy
+
+ belongs_to :teacher, class_name: 'User', foreign_key: :tea_id # 定义一个方法teacher,该方法通过tea_id来调用User表
+ belongs_to :school, class_name: 'School', foreign_key: :school_id #定义一个方法school,该方法通过school_id来调用School表
+ belongs_to :course_list
+
+ has_many :course_infos, dependent: :destroy
+ # 课堂左侧导航栏的模块
+ has_many :course_modules, dependent: :destroy
+ has_many :board_course_modules, -> { board_module }, class_name: "CourseModule"
+ has_many :attachment_course_modules, -> { attachment_module }, class_name: "CourseModule"
+ has_many :common_course_modules, -> { common_homework_module }, class_name: "CourseModule"
+ has_many :group_course_modules, -> { group_homework_module }, class_name: "CourseModule"
+ has_many :shixun_course_modules, -> { shixun_homework_module }, class_name: "CourseModule"
+
+ # 课堂模块的二级目录
+ has_many :course_second_categories, dependent: :destroy
+ # 课堂分班
+ has_many :course_groups, dependent: :destroy
+ # 答辩组
+ has_many :graduation_groups, dependent: :destroy
+
+ has_many :course_members, dependent: :destroy
+ has_many :course_messages, dependent: :destroy
+ has_many :homework_commons, dependent: :destroy
+ has_many :homework_group_settings
+ has_many :graduation_works, dependent: :destroy
+
+ # 实训作业的二级目录(已弃用)
+ has_many :course_homework_categories, dependent: :destroy
+ has_many :exercises, dependent: :destroy
+
+ #课堂的试卷
+ has_many :exercise_group_settings, :dependent => :destroy
+
+ # 课堂的问卷
+ has_many :polls, dependent: :destroy
+ has_many :poll_group_settings, :dependent => :destroy
+
+ # 毕业设计
+ has_many :graduation_topics, dependent: :destroy
+ has_many :graduation_tasks, dependent: :destroy
+ has_many :student_graduation_topics, :dependent => :destroy
+ has_many :teacher_course_groups, :dependent => :destroy
+
+ # 资源
+ has_many :attachments, as: :container, dependent: :destroy
+ has_many :attachment_group_settings, :dependent => :destroy
+
+ # 课堂学生,弃用
+ has_many :student, :class_name => 'StudentsForCourse', :source => :user
+
+
+ # 课堂动态
+ has_many :course_acts, class_name: 'CourseActivity', as: :course_act, dependent: :destroy
+ has_many :tidings, as: :container, dependent: :destroy
+
+ # 老版的members弃用 现用course_members
+ has_many :members
+
+ scope :hidden, ->(is_hidden = true) { where(is_hidden: is_hidden) }
+ scope :ended, ->(is_end = true) { where(is_end: is_end) }
+ scope :deleted, ->(is_delete = 1) { where(is_delete: is_delete) }
+ scope :by_user, ->(user) { joins(:course_members).where('course_members.user_id = ?', user.id).order(updated_at: :desc) }
+ scope :by_keywords, lambda { |keywords|
+ where("name LIKE ?", "%#{keywords.split(" ").join('|')}%") unless keywords.blank?
+ }
+
+ acts_as_taggable
+
+
+ # 课程权限判断
+ ADMIN = 0 # 超级管理员
+ BUSINESS = 1 # 运营人员
+ CREATOR = 2 # 课程创建者
+ PROFESSOR = 3 # 课程老师
+ ASSISTANT_PROFESSOR = 4 # 课程助教
+ STUDENT = 5 # 学生
+ NORMAL = 6 # 普通用户
+ Anonymous = 7 # 普未登录
+
+ validates :name, presence: true, length: { maximum: 30 }
+
+ after_create :create_board_sync, :act_as_course_activity, :send_tiding
+
+ def course_member? user_id, role
+ course_members.where(user_id: user_id, role: role).exists?
+ end
+
+ # 作业对应的子目录/父目录名称
+ def category_info type
+ course_module = course_modules.find_by(module_type: type)
+ { category_id: course_module&.id, category_name: course_module&.module_name }
+ end
+
+ # 未分班的学生数
+ def none_group_count
+ course_members.where(role: 4, course_group_id: 0).count
+ end
+
+ def course_member(user_id)
+ course_members.find_by(user_id: user_id, is_active: 1)
+ end
+
+ def course_student(user_id)
+ course_members.find_by(user_id: user_id, role: %i(STUDENT))
+ end
+
+
+ def teacher_group(user_id)
+ data =
+ if teacher_course_groups.exists?(user_id: user_id)
+ teacher_course_groups.joins(:course_group).where(user_id: user_id)
+ .pluck('course_groups.id', 'course_groups.name')
+ else
+ course_groups.pluck(:id, :name)
+ end
+
+ data.map { |arr| { group_id: arr.first, group_name: arr.last } }
+ end
+
+ #当前老师的班级id
+ def teacher_course_group_ids(user_id)
+ course_teacher_member = teacher_course_groups.get_user_groups(user_id) #获取当前老师的分班
+ if course_teacher_member.blank?
+ if none_group_count > 0 #有未分班的,则发布到未发布分班
+ un_group_ids = [0]
+ else
+ un_group_ids = []
+ end
+ course_groups.pluck(:id) + un_group_ids #所有分班和未分班
+ else
+ course_teacher_member.pluck(:course_group_id).reject(&:blank?).uniq #当前用户所在的班级,老师可能有多个班级
+ end
+ end
+
+ # 查询老师分班的所有学生
+ def teacher_group_user_ids user_id
+ teachers = teacher_course_groups.where(user_id: user_id)
+ if teachers.exists?
+ course_members.where(course_group_id: teachers.pluck(:course_group_id)).pluck(:user_id)
+ else
+ course_members.where(role: 4).pluck(:user_id)
+ end
+ end
+
+ # 创建课程模块
+ def create_course_modules(course_module_types)
+ course_modules.destroy_all if course_modules.present?
+
+ all_course_module_types.each do |type|
+ name = get_name_by_type(type)
+ position = get_position_by_type(type)
+
+ hidden = course_module_types.include?(type) ? 0 : 1
+ CourseModule.create(course_id: id, module_type: type, position: position, hidden: hidden, module_name: name)
+ end
+ end
+
+ # 更新课程模块
+ def update_course_modules(course_module_types)
+ all_course_module_types.each do |type|
+ hidden_value = course_module_types.include?(type) ? 0 : 1
+
+ course_module = get_course_module_by_type(type, self.id)
+ course_module.update_attribute(:hidden, hidden_value) if course_module.present?
+ end
+ end
+
+ def all_course_module_types
+ %w[activity shixun_homework common_homework group_homework graduation exercise poll attachment board course_group]
+ end
+
+ def get_course_module_by_type(type, course_id)
+ CourseModule.where(course_id: course_id, module_type: type).first
+ end
+
+ # 创建课程讨论区
+ def create_board_sync
+ boards.create(name: '讨论区', description: name, project_id: -1)
+ end
+
+ def delete!
+ update_attribute(:is_delete, true)
+ end
+
+ def attachment_count
+ Attachment.where(container: self).count
+ end
+
+ # 课堂某角色的成员数量:[1, 2, 3] 是教师身份、4 学生身份
+ def course_member_count(roles)
+ course_members.where(role: roles).size
+ end
+
+ # 课堂老师
+ def teachers
+ course_members.where(role: %i[CREATOR PROFESSOR ASSISTANT_PROFESSOR])
+ end
+
+ def teachers_without_assistant_professor
+ course_members.where(role: %i[CREATOR PROFESSOR])
+ end
+
+ # 课堂学生
+ def students
+ course_members.where(role: %i[STUDENT])
+ end
+
+ # 更新课程的访问人数
+ def update_visits(new_visits)
+ update_attributes(visits: new_visits)
+ end
+
+ # 老师负责的分班id
+ def charge_group_ids user
+ member = course_member(user.id)
+ group_ids = if member.present?
+ member.teacher_course_groups.size > 0 ? member.teacher_course_groups.pluck(:course_group_id) : course_groups.pluck(:id)
+ elsif user.admin?
+ course_groups.pluck(:id)
+ else
+ []
+ end
+ end
+
+ # 生成邀请码
+ CODES = %W(2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z)
+ def generate_invite_code
+ return invite_code if invite_code.present? && invite_code.size >= 5
+
+ code = CODES.sample(5).join
+ while Course.exists?(invite_code: code) do
+ code = CODES.sample(5).join
+ end
+ update_attribute(:invite_code, code)
+
+ code
+ end
+
+ # 课堂主讨论区
+ def course_board
+ board = boards.find_by(parent_id: 0)
+ return board if board.present?
+
+ create_board_sync
+ Board.find_by(parent_id: 0, course_id: id)
+ end
+
+ # 是否是课堂的成员(未实现,暂时返回true)
+ def member?(user)
+ true
+ end
+
+ # 是否具有分班权限,返回分班的id
+ def group_course_power(user_id)
+ teacher_course_groups.where(user_id: user_id).pluck(:id)
+ end
+
+ #课程动态公共表记录
+ def act_as_course_activity
+ self.course_acts << CourseActivity.new(user_id: tea_id, course_id: id)
+ end
+
+ # 当前老师分班下的所有学生
+ def user_group_students(user_id)
+ group_ids = teacher_course_groups.where(user_id: user_id).pluck(:course_group_id)
+ course_members.where(course_group_id: group_ids)
+ end
+
+ def self_duplicate
+ DuplicateCourseService.call(self, User.current)
+ end
+
+ def update_quotes attachment
+ if attachment.copy_from
+ attachments = Attachment.find_by_sql("select * from attachments where copy_from = #{attachment.copy_from} or id = #{attachment.copy_from}")
+ else
+ attachments = Attachment.find_by_sql("select * from attachments where copy_from = #{attachment.id} or id = #{attachment.copy_from}")
+ end
+ attachment.quotes = get_qute_number attachment
+ attachment.save
+ attachments.each do |att|
+ att.quotes = attachment.quotes
+ att.save
+ end
+ end
+
+ def get_qute_number attachment
+ if attachment.copy_from
+ result = Attachment.find_by_sql("select count(*) as number from attachments where copy_from = #{attachment.copy_from}")
+ else
+ result = Attachment.find_by_sql("select count(*) as number from attachments where copy_from = #{attachment.id}")
+ end
+ if result.nil? || result.count <= 0
+ 0
+ else
+ result[0].number
+ end
+ end
+
+ #获取试卷/问卷已发布的班级id,名称和人数。当为统一设置时,显示全部,否则只显示当前已发布的班级信息
+ def get_ex_published_course(common_ids)
+ teacher_power_courses = []
+ if course_groups.present?
+ common_ids.each do |i|
+ student_count = students.where(course_group_id:i).count
+ if i == 0
+ teacher_power_courses << {course_name:"未分班",course_id:0,student_count:student_count}
+ else
+ course_group_name = course_groups.find_by(id:i)
+ teacher_power_courses << {course_name:course_group_name&.name,course_id:i,student_count:student_count}
+ end
+ end
+ end
+ teacher_power_courses
+ end
+
+ private
+
+ #创建课程后,给该用户发送消息
+ def send_tiding
+ self.tidings << Tiding.new(user_id: tea_id, trigger_user_id: tea_id, belong_container_id: id,
+ belong_container_type: 'Course', tiding_type: 'System')
+ end
+
+ def get_name_by_type(type)
+ case type
+ when 'activity' then '动态'
+ when 'shixun_homework' then '实训作业'
+ when 'common_homework' then '普通作业'
+ when 'group_homework' then '分组作业'
+ when 'graduation' then '毕业设计'
+ when 'exercise' then '试卷'
+ when 'poll' then '问卷'
+ when 'attachment' then '资源'
+ when 'board' then '讨论'
+ when 'course_group' then '分班'
+ else ''
+ end
+ end
+
+ def get_position_by_type(type)
+ case type
+ when 'activity' then 1
+ when 'shixun_homework' then 2
+ when 'common_homework' then 3
+ when 'group_homework' then 4
+ when 'graduation' then 5
+ when 'exercise' then 6
+ when 'poll' then 7
+ when 'attachment' then 8
+ when 'board' then 9
+ when 'course_group' then 10
+ else 100
+ end
+ end
+end
diff --git a/app/models/course_activity.rb b/app/models/course_activity.rb
new file mode 100644
index 000000000..b2ea0b146
--- /dev/null
+++ b/app/models/course_activity.rb
@@ -0,0 +1,37 @@
+class CourseActivity < ApplicationRecord
+ belongs_to :course_act, polymorphic: true
+ belongs_to :course
+ belongs_to :user
+ belongs_to :exercise
+ belongs_to :poll
+
+ after_create :add_course_lead
+
+ # 发布新课导语
+ # 导语要放置在课程创建信息之后
+ def add_course_lead
+ # 避免空数据迁移报错问题
+ if self.course_act_type == "Course"
+ sample = PlatformSample.where(:samples_type => "courseGuide").first
+ if sample.present? && sample.contents.present?
+ content = sample.contents
+ elsif Message.find(12440)
+ lead_message = Message.find(12440)
+ content = lead_message.content
+ end
+ if content
+ # message的status状态为0为正常,为1表示创建课程时发送的message
+ # author_id 默认为课程使者创建
+ message = Message.create(subject: "新课导语",
+ board_id: course.course_board.try(:id),
+ author_id: 1,
+ sticky: true,
+ status: true,
+ message_detail_attributes: {content: content}
+ )
+ # 更新的目的是为了排序,因为该条动态的时间可能与课程创建的动态创建时间一致
+ message.course_acts.first.update_attribute(:updated_at, message.course_acts.first.updated_at + 1) if message.course_acts.first
+ end
+ end
+ end
+end
diff --git a/app/models/course_group.rb b/app/models/course_group.rb
new file mode 100644
index 000000000..33e40ee52
--- /dev/null
+++ b/app/models/course_group.rb
@@ -0,0 +1,32 @@
+class CourseGroup < ApplicationRecord
+ default_scope { order("course_groups.position ASC") }
+ belongs_to :course, counter_cache: true
+ has_many :course_members
+
+ has_many :exercise_group_settings,:dependent => :destroy
+ has_many :attachment_group_settings, :dependent => :destroy
+ has_many :homework_group_reviews, :dependent => :destroy
+ scope :by_group_ids, lambda { |ids| where(id: ids)}
+
+ validates :name, length: { maximum: 20 }
+
+ after_create :generate_invite_code
+
+ # 延迟生成邀请码
+ def invite_code
+ return generate_invite_code
+ end
+
+ # 生成邀请码
+ CODES = %W(2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z)
+ def generate_invite_code
+ code = read_attribute(:invite_code)
+ if !code || code.size < 6
+ code = CODES.sample(6).join
+ return generate_invite_code if CourseGroup.where(invite_code: code).present?
+ update_attribute(:invite_code, code)
+ end
+ code
+ end
+
+end
diff --git a/app/models/course_homework_category.rb b/app/models/course_homework_category.rb
new file mode 100644
index 000000000..79f105da8
--- /dev/null
+++ b/app/models/course_homework_category.rb
@@ -0,0 +1,3 @@
+class CourseHomeworkCategory < ApplicationRecord
+ belongs_to :course
+end
diff --git a/app/models/course_info.rb b/app/models/course_info.rb
new file mode 100644
index 000000000..1cd0a763c
--- /dev/null
+++ b/app/models/course_info.rb
@@ -0,0 +1,3 @@
+class CourseInfo < ApplicationRecord
+ belongs_to :course
+end
diff --git a/app/models/course_list.rb b/app/models/course_list.rb
new file mode 100644
index 000000000..080c05ae2
--- /dev/null
+++ b/app/models/course_list.rb
@@ -0,0 +1,8 @@
+class CourseList < ApplicationRecord
+ has_many :courses
+ has_many :question_banks
+ has_many :homework_banks
+ has_many :exercise_banks
+ has_many :gtask_banks
+ has_many :gtopic_banks
+end
diff --git a/app/models/course_member.rb b/app/models/course_member.rb
new file mode 100644
index 000000000..d9995d463
--- /dev/null
+++ b/app/models/course_member.rb
@@ -0,0 +1,159 @@
+class CourseMember < ApplicationRecord
+ # role 1:创建者 2:老师 3:助教 4:学生
+ enum role: { CREATOR: 1, PROFESSOR: 2, ASSISTANT_PROFESSOR: 3, STUDENT: 4 }
+ # is_active: true:当前活跃身份(多重身份的用户)
+
+ belongs_to :course, counter_cache: true
+ belongs_to :user
+ belongs_to :course_group, counter_cache: true, optional: true
+ belongs_to :graduation_group, optional: true
+ has_many :teacher_course_groups, dependent: :destroy
+
+ scope :teachers_and_admin, -> { where(role: %i[CREATOR PROFESSOR ASSISTANT_PROFESSOR]) }
+ scope :students, ->(course) { where(course_id: course.id, role: %i[STUDENT])}
+ scope :course_find_by_ids, lambda { |k,ids| where("#{k}": ids)}
+ scope :course_students, -> {where(role: %i[STUDENT])}
+
+ #用户的身份查询
+ scope :course_user_role, lambda { |k| where(role: k)}
+
+ # 未分班
+ scope :ungroup_students, -> { where(course_group_id: 0, role: 4) }
+
+ after_destroy :delete_works
+ after_create :work_operation
+ def delete_works
+ if self.role == "STUDENT"
+ course = self.course
+ student_works = StudentWork.joins(:homework_common).where(user_id: self.user_id, homework_commons: {course_id: course.id})
+ student_works.update_all(is_delete: 1)
+
+ exercise_users = ExerciseUser.joins(:exercise).where(user_id: self.user_id, exercises: {course_id: course.id})
+ exercise_users.update_all(is_delete: 1)
+
+ poll_users = PollUser.joins(:poll).where(user_id: self.user_id, polls: {course_id: course.id})
+ poll_users.update_all(is_delete: 1)
+
+ course.graduation_works.where(user_id: self.user_id).update_all(is_delete: 1)
+ end
+ end
+
+ def work_operation
+ if self.role == "STUDENT"
+ recover_works
+ create_exercise_users
+ create_graduation_works
+ create_poll_users
+ create_student_works
+ end
+ end
+
+ # 加入班级时还原作品(如果有已删除作品的话)
+ def recover_works
+ course = self.course
+
+ student_works = StudentWork.where(user_id: self.user_id, homework_common_id: course.homework_commons)
+ student_works.update_all(is_delete: 0)
+
+ exercise_users = ExerciseUser.where(user_id: self.user_id, exercise_id: course.exercises)
+ exercise_users.update_all(is_delete: 0)
+
+ poll_users = PollUser.where(user_id: self.user_id, poll_id: course.polls)
+ poll_users.update_all(is_delete: 0)
+
+ graduation_works = course.graduation_works.where(user_id: self.user_id)
+ graduation_works.update_all(is_delete: 0)
+ end
+
+ # 加入班级时创建作业的作品(如果没有作品才创建)
+ def create_student_works
+ course = self.course
+
+ homework_commons = course.homework_commons.where(homework_type: %i[normal group practice])
+ str = ""
+ homework_commons.each do |homework|
+ next if homework.student_works.where(user_id: self.user_id).any?
+ str += "," if str != ""
+ str += "(#{homework.id}, #{user_id}, '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
+ end
+
+ if str != ""
+ sql = "insert into student_works (homework_common_id, user_id, created_at, updated_at) values" + str
+ ActiveRecord::Base.connection.execute sql
+ end
+ end
+
+ # 加入班级时创建已发布试卷的作品(如果没有作品才创建)
+ def create_exercise_users
+ course = self.course
+ exercises = course.exercises
+
+ str = ""
+ exercises.each do |exercise|
+ next if exercise.exercise_users.where(user_id: self.user_id).any?
+ str += "," if str != ""
+ str += "(#{user_id}, #{exercise.id}, 0, '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
+ end
+
+ if str != ""
+ sql = "insert into exercise_users (user_id, exercise_id, commit_status, created_at, updated_at) values" + str
+ ActiveRecord::Base.connection.execute sql
+ end
+ end
+
+ # 加入班级时创建已发布问卷的作品(如果没有作品才创建)
+ def create_poll_users
+ course = self.course
+ polls = course.polls
+
+ str = ""
+ polls.each do |poll|
+ next if poll.poll_users.where(user_id: self.user_id).any?
+ str += "," if str != ""
+ str += "(#{user_id}, #{poll.id}, 0, '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
+ end
+
+ if str != ""
+ sql = "insert into poll_users (user_id, poll_id, commit_status, created_at, updated_at) values" + str
+ ActiveRecord::Base.connection.execute sql
+ end
+ end
+
+ # 创建毕设任务作品(如果没有作品才创建)
+ def create_graduation_works
+ course = self.course
+ tasks = course.graduation_tasks
+
+ str = ""
+ tasks.each do |task|
+ next if task.graduation_works.where(user_id: self.user_id).any?
+ str += "," if str != ""
+ str += "(#{task.id}, #{user_id}, #{course_id}, '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
+ end
+
+ if str != ""
+ sql = "insert into graduation_works (graduation_task_id, user_id, course_id, created_at, updated_at) values" + str
+ ActiveRecord::Base.connection.execute sql
+ end
+ end
+
+ # 分班名称
+ def course_group_name
+ self.course_group_id == 0 ? "未分班" : course_group.try(:name)
+ end
+
+ # 学生的分班老师
+ def member_teachers
+ teacher_groups = course.teacher_course_groups
+ if teacher_groups.count > 0
+ member_ids = teacher_groups.where(course_group_id: self.try(:course_group_id)).pluck(:course_member_id)
+
+ none_group_teachers = teacher_groups.pluck(:course_member_id).size > 0 ? teacher_groups.pluck(:course_member_id).join(',') : -1
+ teachers = course.teachers.where("members.id not in (#{none_group_teachers}) or
+ members.id in (#{member_ids.size > 0 ? member_ids.join(',') : -1})")
+ else
+ teachers = course.teachers
+ end
+ teachers
+ end
+end
diff --git a/app/models/course_message.rb b/app/models/course_message.rb
new file mode 100644
index 000000000..acb45d5f4
--- /dev/null
+++ b/app/models/course_message.rb
@@ -0,0 +1,37 @@
+class CourseMessage < ApplicationRecord
+ enum status: { UNHANDLED: 0, PASSED: 1, REJECTED: 2 }
+ belongs_to :course
+ belongs_to :user
+
+ scope :find_by_course, ->(course) { where(course_id: course.id) }
+ scope :join_course_requests, -> { where(course_message_type: "JoinCourseRequest") }
+ scope :unhandled, -> { where(status: :UNHANDLED) }
+
+ scope :unhandled_join_course_requests_by_course, ->(course) { find_by_course(course).join_course_requests.unhandled }
+
+ def pass!
+ update!(status: :PASSED)
+ send_deal_tiding
+ end
+
+ def application_user
+ User.find_by(id: course_message_id)
+ end
+
+ def reject!
+ update!(status: :REJECTED)
+ send_deal_tiding
+ end
+
+ private
+
+ def send_deal_tiding
+ # 发送申请处理结果消息
+ Tiding.create!(
+ user_id: user_id, trigger_user: User.current, container_id: course_id, container_type: 'DealCourse',
+ belong_container: course, extra: content.to_i == 2 ? '7' : '9', tiding_type: 'System', status: status == :PASSED ? 1 : 2
+ )
+ # 将申请消息置为已处理
+ Tiding.where(trigger_user_id: user_id, container_id: course_id, container_type: 'JoinCourse', status: 0).update_all(status: 1)
+ end
+end
\ No newline at end of file
diff --git a/app/models/course_module.rb b/app/models/course_module.rb
new file mode 100644
index 000000000..7b96a044f
--- /dev/null
+++ b/app/models/course_module.rb
@@ -0,0 +1,27 @@
+class CourseModule < ApplicationRecord
+ default_scope { order("course_modules.position ASC") }
+ belongs_to :course
+
+ # 二级目录
+ has_many :course_second_categories
+
+ validates :module_name, length: { maximum: 20 }
+
+ scope :graduation_module, -> { where(module_type: "graduation") }
+ scope :graduation_module_not_hidden, -> { graduation_module.where(hidden: 0) }
+ scope :board_module, -> { where(module_type: 'board') }
+ scope :attachment_module, -> { includes(:course_second_categories).where(module_type: 'attachment') }
+ scope :common_homework_module, -> { where(module_type: 'common_homework') }
+ scope :group_homework_module, -> { where(module_type: 'group_homework') }
+ scope :shixun_homework_module, -> { where(module_type: 'shixun_homework') }
+ scope :search_by_module_type, -> (type) {where(module_type:type)}
+
+ # 课堂模块的子目录
+ def course_second_categories
+ if module_type == "graduation" && CourseSecondCategory.where(course_module_id: self.id).count == 0
+ CourseSecondCategory.create!(course_module_id: self.id, course_id: self.course_id, name: "毕设选题", category_type: "graduation", position: 1)
+ CourseSecondCategory.create!(course_module_id: self.id, course_id: self.course_id, name: "毕设任务", category_type: "graduation", position: 2)
+ end
+ CourseSecondCategory.where(course_module_id: self.id)
+ end
+end
diff --git a/app/models/course_second_category.rb b/app/models/course_second_category.rb
new file mode 100644
index 000000000..125a81cb6
--- /dev/null
+++ b/app/models/course_second_category.rb
@@ -0,0 +1,15 @@
+class CourseSecondCategory < ApplicationRecord
+ default_scope { order("course_second_categories.position ASC") }
+
+ belongs_to :course
+ belongs_to :course_module
+ has_many :homework_commons
+
+ validates :name, length: { maximum: 30 }
+
+ def category_type_str
+ category_type == "graduation" && name == "毕设选题" ? "graduation_topics" : (
+ category_type == "graduation" && name == "毕设任务" ? "graduation_tasks" : category_type
+ )
+ end
+end
diff --git a/app/models/department.rb b/app/models/department.rb
new file mode 100644
index 000000000..15a8a7c1b
--- /dev/null
+++ b/app/models/department.rb
@@ -0,0 +1,5 @@
+class Department < ApplicationRecord
+ belongs_to :school
+
+ has_many :department_members, dependent: :destroy
+end
diff --git a/app/models/department_member.rb b/app/models/department_member.rb
new file mode 100644
index 000000000..f1f3859ec
--- /dev/null
+++ b/app/models/department_member.rb
@@ -0,0 +1,4 @@
+class DepartmentMember < ApplicationRecord
+ belongs_to :user
+ belongs_to :department
+end
diff --git a/app/models/discuss.rb b/app/models/discuss.rb
new file mode 100644
index 000000000..602813e66
--- /dev/null
+++ b/app/models/discuss.rb
@@ -0,0 +1,52 @@
+class Discuss < ApplicationRecord
+ default_scope { order(created_at: :desc) }
+
+ belongs_to :user
+ belongs_to :parent, class_name: 'Discuss', foreign_key: :parent_id, optional: true
+
+ has_many :children, -> { reorder(created_at: :asc) }, class_name: 'Discuss', foreign_key: :parent_id
+ has_many :praise_tread, as: :praise_tread_object, dependent: :destroy
+ has_many :tidings, as: :container, dependent: :destroy
+ has_one :praise_tread_cache, as: :object, dependent: :destroy
+
+ after_create :send_tiding
+
+ scope :children, -> (discuss_id){ where(parent_id: discuss_id).includes(:user).reorder(created_at: :asc) }
+
+ def has_parent?
+ parent_id.present?
+ end
+
+ def username
+ user.full_name
+ end
+
+ def can_deleted?(user)
+ user.admin? || user.id == user_id
+ end
+
+ def game_url(shixun, user)
+ return '' unless shixun.has_manager?(user)
+
+ game = Game.joins(:challenge).where(challenges: { shixun_id: shixun.id, position: position || 1 })
+ .select(:identifier).find_by(user_id: user_id)
+ "/tasks/#{game&.identifier}"
+ end
+
+ def contents(shixun, user)
+ return content unless hidden?
+
+ shixun.has_manager?(user) ? content : '违规评论已被屏蔽!'
+ end
+
+ private
+
+ def send_tiding
+ base_attrs = {
+ trigger_user_id: user_id, parent_container_id: challenge_id, parent_container_type: 'Challenge',
+ belong_container_id: dis_id, belong_container_type: 'Shixun', viewed: 0, tiding_type: 'Comment'
+ }
+ user_id = has_parent? ? parent.user_id : Challenge.find(challenge_id).user_id
+ tidings.create!(base_attrs.merge(user_id: user_id))
+ end
+end
diff --git a/app/models/ec_achievement_evaluation_relate.rb b/app/models/ec_achievement_evaluation_relate.rb
new file mode 100644
index 000000000..246e4c2e4
--- /dev/null
+++ b/app/models/ec_achievement_evaluation_relate.rb
@@ -0,0 +1,4 @@
+class EcAchievementEvaluationRelate < ApplicationRecord
+ belongs_to :ec_course_achievement_method
+ belongs_to :ec_course_evaluation_subitem
+end
diff --git a/app/models/ec_course.rb b/app/models/ec_course.rb
new file mode 100644
index 000000000..6f5480ea8
--- /dev/null
+++ b/app/models/ec_course.rb
@@ -0,0 +1,27 @@
+class EcCourse < ApplicationRecord
+ belongs_to :ec_year
+
+ # 课程目标
+ has_many :ec_course_targets, dependent: :destroy
+ has_many :ec_graduation_subitem_course_targets, through: :ec_course_targets
+ # 课程负责教师
+ has_many :ec_course_users
+ has_many :course_managers, through: :ec_course_users, class_name: 'User'
+ # 课程考核标准
+ has_many :ec_course_evaluations, dependent: :destroy
+ # 课程等级
+ has_many :ec_score_levels, dependent: :destroy
+ # 课程支撑
+ has_many :ec_course_supports, dependent: :destroy
+ has_many :ec_graduation_subitems, through: :ec_course_supports
+ # 达成评价
+ has_many :ec_course_student_scores, dependent: :destroy
+ # 工程课堂和educoder课堂关联
+ has_many :ec_major_courses, dependent: :destroy
+ has_many :courses, through: :ec_major_courses
+
+ # 课程目标达成方法
+ # has_many :ec_course_achievement_methods
+
+ accepts_nested_attributes_for :ec_course_targets, :ec_score_levels, allow_destroy: true
+end
diff --git a/app/models/ec_course_achievement_method.rb b/app/models/ec_course_achievement_method.rb
new file mode 100644
index 000000000..bf27eab62
--- /dev/null
+++ b/app/models/ec_course_achievement_method.rb
@@ -0,0 +1,12 @@
+class EcCourseAchievementMethod < ApplicationRecord
+ belongs_to :ec_course
+ belongs_to :ec_course_target
+ belongs_to :ec_course_evaluation
+
+ has_many :ec_achievement_evaluation_relates, dependent: :delete_all
+ has_many :ec_course_evaluation_subitems, through: :ec_achievement_evaluation_relates
+
+ validates :percentage, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }
+
+ accepts_nested_attributes_for :ec_achievement_evaluation_relates, allow_destroy: true
+end
diff --git a/app/models/ec_course_evaluation.rb b/app/models/ec_course_evaluation.rb
new file mode 100644
index 000000000..6b778de66
--- /dev/null
+++ b/app/models/ec_course_evaluation.rb
@@ -0,0 +1,18 @@
+# 工程认证课程达成度计算--课程评估标准
+# status: 1 单次考核总成绩支撑课程目标, 2 单次考核的某分项成绩支撑课程目标
+# score_type: 1:导入的是明细成绩 2:导入的是平均成绩
+class EcCourseEvaluation < ApplicationRecord
+ belongs_to :ec_course
+ # 考核分项
+ has_many :ec_course_evaluation_subitems, dependent: :destroy
+ has_many :ec_student_achievements
+
+ enum status: { partly: 1, totality: 2 }, _suffix: :support # :partly_support?, :totality_support?
+ enum score_type: { detail: 1, average: 2 }, _suffix: :score_type # :detail_score_type?, :average_score_type?
+
+ accepts_nested_attributes_for :ec_course_evaluation_subitems, allow_destroy: true
+
+ def imported?
+ import_status?
+ end
+end
diff --git a/app/models/ec_course_evaluation_subitem.rb b/app/models/ec_course_evaluation_subitem.rb
new file mode 100644
index 000000000..e23254cc2
--- /dev/null
+++ b/app/models/ec_course_evaluation_subitem.rb
@@ -0,0 +1,6 @@
+class EcCourseEvaluationSubitem < ApplicationRecord
+ belongs_to :ec_course_evaluation
+
+ has_many :ec_achievement_evaluation_relates, dependent: :delete_all
+ has_many :ec_student_achievements, dependent: :delete_all
+end
diff --git a/app/models/ec_course_student_score.rb b/app/models/ec_course_student_score.rb
new file mode 100644
index 000000000..5b45e34a9
--- /dev/null
+++ b/app/models/ec_course_student_score.rb
@@ -0,0 +1,7 @@
+class EcCourseStudentScore < ApplicationRecord
+ belongs_to :ec_year_student
+ belongs_to :ec_course
+ belongs_to :ec_course_target
+
+ has_many :ec_student_score_targets, dependent: :delete_all
+end
\ No newline at end of file
diff --git a/app/models/ec_course_support.rb b/app/models/ec_course_support.rb
new file mode 100644
index 000000000..a6ca96ea9
--- /dev/null
+++ b/app/models/ec_course_support.rb
@@ -0,0 +1,13 @@
+class EcCourseSupport < ApplicationRecord
+ default_scope { order(position: :asc) }
+
+ belongs_to :ec_course
+ belongs_to :ec_graduation_subitem
+ # TODO: 将 ec_graduation_subitem_courses 移除,这个表作为关系表
+
+ has_one :ec_graduation_requirement_calculation, dependent: :destroy
+
+ validates :weights, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }
+
+ number_display_methods :weights
+end
\ No newline at end of file
diff --git a/app/models/ec_course_target.rb b/app/models/ec_course_target.rb
new file mode 100644
index 000000000..9b93cb73c
--- /dev/null
+++ b/app/models/ec_course_target.rb
@@ -0,0 +1,19 @@
+# TODO:: change table column :weigths => :weight
+class EcCourseTarget < ApplicationRecord
+ belongs_to :ec_course
+
+ has_many :ec_graduation_subitem_course_targets, dependent: :destroy
+ has_many :ec_graduation_subitems, through: :ec_graduation_subitem_course_targets
+ has_many :ec_student_score_targets
+ has_many :ec_course_achievement_methods, dependent: :destroy
+ has_many :ec_achievement_evaluation_relates, dependent: :destroy
+
+ validates :content, presence: true
+ validates :standard_grade, numericality: { only_integer: true, greater_than: 0 }
+ validates :weight, presence: true, numericality: { less_than_or_equal_to: 1, greater_than_or_equal_to: 0 }
+
+ accepts_nested_attributes_for :ec_graduation_subitem_course_targets, allow_destroy: true
+ accepts_nested_attributes_for :ec_course_achievement_methods, allow_destroy: true
+
+ number_display_methods :weight
+end
diff --git a/app/models/ec_course_user.rb b/app/models/ec_course_user.rb
new file mode 100644
index 000000000..8ef6cd721
--- /dev/null
+++ b/app/models/ec_course_user.rb
@@ -0,0 +1,5 @@
+class EcCourseUser < ApplicationRecord
+ belongs_to :ec_course
+ belongs_to :user
+ belongs_to :ec_year
+end
diff --git a/app/models/ec_graduation_requirement.rb b/app/models/ec_graduation_requirement.rb
new file mode 100644
index 000000000..d0f4195d0
--- /dev/null
+++ b/app/models/ec_graduation_requirement.rb
@@ -0,0 +1,11 @@
+class EcGraduationRequirement < ApplicationRecord
+ belongs_to :ec_year
+
+ has_many :ec_graduation_subitems, dependent: :destroy
+ has_many :ec_requirement_vs_objectives, dependent: :destroy
+
+ validates :position, presence: true, numericality: { only_integer: true, greater_than: 0 }
+ validates :content, presence: true
+
+ default_scope { order(position: :asc) }
+end
diff --git a/app/models/ec_graduation_requirement_calculation.rb b/app/models/ec_graduation_requirement_calculation.rb
new file mode 100644
index 000000000..2f9c92f71
--- /dev/null
+++ b/app/models/ec_graduation_requirement_calculation.rb
@@ -0,0 +1,3 @@
+class EcGraduationRequirementCalculation < ApplicationRecord
+ belongs_to :ec_course_support
+end
\ No newline at end of file
diff --git a/app/models/ec_graduation_standard.rb b/app/models/ec_graduation_standard.rb
new file mode 100644
index 000000000..ba653ab86
--- /dev/null
+++ b/app/models/ec_graduation_standard.rb
@@ -0,0 +1,5 @@
+class EcGraduationStandard < ApplicationRecord
+ has_many :ec_require_sub_vs_standards, dependent: :destroy
+
+ validates :content, presence: true
+end
diff --git a/app/models/ec_graduation_subitem.rb b/app/models/ec_graduation_subitem.rb
new file mode 100644
index 000000000..2104f5c40
--- /dev/null
+++ b/app/models/ec_graduation_subitem.rb
@@ -0,0 +1,18 @@
+class EcGraduationSubitem < ApplicationRecord
+
+ default_scope { order(position: :asc) }
+
+ belongs_to :ec_graduation_requirement
+
+ has_many :ec_require_sub_vs_standards, dependent: :destroy
+ has_many :ec_course_supports, dependent: :destroy
+ has_many :ec_courses, through: :ec_course_supports
+
+ has_many :ec_graduation_subitem_course_targets, dependent: :delete_all
+ has_many :ec_course_targets, through: :ec_graduation_subitem_course_targets
+
+ validates :position, presence: true, numericality: { only_integer: true, greater_than: 0 }
+ validates :content, presence: true
+
+ accepts_nested_attributes_for :ec_course_supports, allow_destroy: true
+end
diff --git a/app/models/ec_graduation_subitem_course_target.rb b/app/models/ec_graduation_subitem_course_target.rb
new file mode 100644
index 000000000..3dffe2c21
--- /dev/null
+++ b/app/models/ec_graduation_subitem_course_target.rb
@@ -0,0 +1,4 @@
+class EcGraduationSubitemCourseTarget < ApplicationRecord
+ belongs_to :ec_course_target
+ belongs_to :ec_graduation_subitem
+end
\ No newline at end of file
diff --git a/app/models/ec_major.rb b/app/models/ec_major.rb
new file mode 100644
index 000000000..4842f7d70
--- /dev/null
+++ b/app/models/ec_major.rb
@@ -0,0 +1,7 @@
+class EcMajor < ApplicationRecord
+ # 主页对应的学校,不同学校可以选用同样的专业,而每个专业又各具特色
+ has_many :schools, through: :ec_major_schools
+ has_many :ec_major_schools, dependent: :destroy
+
+ scope :search_name_or_code, -> (keyword) { where('name LIKE :keyword OR code LIKE :keyword', keyword: "%#{keyword.strip}%") }
+end
diff --git a/app/models/ec_major_course.rb b/app/models/ec_major_course.rb
new file mode 100644
index 000000000..d43f0da9c
--- /dev/null
+++ b/app/models/ec_major_course.rb
@@ -0,0 +1,4 @@
+class EcMajorCourse < ApplicationRecord
+ belongs_to :ec_course
+ belongs_to :course
+end
diff --git a/app/models/ec_major_school.rb b/app/models/ec_major_school.rb
new file mode 100644
index 000000000..41a835f63
--- /dev/null
+++ b/app/models/ec_major_school.rb
@@ -0,0 +1,19 @@
+class EcMajorSchool < ApplicationRecord
+ belongs_to :ec_major
+ belongs_to :school
+
+ # 专业对应的学年
+ has_many :ec_years, dependent: :destroy
+
+ # 学校对应的专业中成员
+ has_many :ec_major_school_users, dependent: :destroy
+ has_many :users, through: :ec_major_school_users
+
+ scope :is_template, -> { where(template_major: true) }
+ scope :not_template, -> { where(template_major: false) }
+
+ # 是否为该专业管理员
+ def manager?(user)
+ ec_major_school_users.exists?(user_id: user.id)
+ end
+end
diff --git a/app/models/ec_major_school_user.rb b/app/models/ec_major_school_user.rb
new file mode 100644
index 000000000..538e5674b
--- /dev/null
+++ b/app/models/ec_major_school_user.rb
@@ -0,0 +1,4 @@
+class EcMajorSchoolUser < ApplicationRecord
+ belongs_to :user
+ belongs_to :ec_major_school
+end
diff --git a/app/models/ec_require_sub_vs_standard.rb b/app/models/ec_require_sub_vs_standard.rb
new file mode 100644
index 000000000..c04ca322b
--- /dev/null
+++ b/app/models/ec_require_sub_vs_standard.rb
@@ -0,0 +1,4 @@
+class EcRequireSubVsStandard < ApplicationRecord
+ belongs_to :ec_graduation_standard
+ belongs_to :ec_graduation_subitem
+end
diff --git a/app/models/ec_requirement_vs_objective.rb b/app/models/ec_requirement_vs_objective.rb
new file mode 100644
index 000000000..88f969e10
--- /dev/null
+++ b/app/models/ec_requirement_vs_objective.rb
@@ -0,0 +1,4 @@
+class EcRequirementVsObjective < ApplicationRecord
+ belongs_to :ec_training_subitem
+ belongs_to :ec_graduation_requirement
+end
diff --git a/app/models/ec_school_user.rb b/app/models/ec_school_user.rb
new file mode 100644
index 000000000..77e069c4d
--- /dev/null
+++ b/app/models/ec_school_user.rb
@@ -0,0 +1,4 @@
+class EcSchoolUser < ApplicationRecord
+ belongs_to :user
+ belongs_to :school
+end
diff --git a/app/models/ec_score_level.rb b/app/models/ec_score_level.rb
new file mode 100644
index 000000000..c9573a9b0
--- /dev/null
+++ b/app/models/ec_score_level.rb
@@ -0,0 +1,11 @@
+class EcScoreLevel < ApplicationRecord
+ belongs_to :ec_course
+
+ default_scope { order(position: :asc) }
+
+ def compare_condition
+ max_position = self.class.maximum(:position)
+ # 特殊情况:只有一条记录
+ position < max_position || max_position == 1 ? 'not_less_than' : 'less_than'
+ end
+end
diff --git a/app/models/ec_student_achievement.rb b/app/models/ec_student_achievement.rb
new file mode 100644
index 000000000..c1363c91c
--- /dev/null
+++ b/app/models/ec_student_achievement.rb
@@ -0,0 +1,5 @@
+class EcStudentAchievement < ApplicationRecord
+ belongs_to :ec_year_student
+ belongs_to :ec_course_evaluation
+ belongs_to :ec_course_evaluation_subitem
+end
diff --git a/app/models/ec_student_score_target.rb b/app/models/ec_student_score_target.rb
new file mode 100644
index 000000000..668e1325b
--- /dev/null
+++ b/app/models/ec_student_score_target.rb
@@ -0,0 +1,5 @@
+class EcStudentScoreTarget < ApplicationRecord
+ belongs_to :ec_year_student
+ belongs_to :ec_course_student_score
+ belongs_to :ec_course_target
+end
\ No newline at end of file
diff --git a/app/models/ec_training_objective.rb b/app/models/ec_training_objective.rb
new file mode 100644
index 000000000..56460561d
--- /dev/null
+++ b/app/models/ec_training_objective.rb
@@ -0,0 +1,11 @@
+class EcTrainingObjective < ApplicationRecord
+
+ belongs_to :ec_year
+
+ # 培养目标分项
+ has_many :ec_training_subitems, dependent: :destroy
+
+ validates :content, presence: true
+
+ accepts_nested_attributes_for :ec_training_subitems, allow_destroy: true
+end
diff --git a/app/models/ec_training_subitem.rb b/app/models/ec_training_subitem.rb
new file mode 100644
index 000000000..0c9c61fbc
--- /dev/null
+++ b/app/models/ec_training_subitem.rb
@@ -0,0 +1,7 @@
+class EcTrainingSubitem < ApplicationRecord
+ belongs_to :ec_training_objective
+
+ has_many :ec_requirement_vs_objectives, foreign_key: :ec_training_objective_id, dependent: :destroy
+
+ validates :content, presence: true
+end
diff --git a/app/models/ec_year.rb b/app/models/ec_year.rb
new file mode 100644
index 000000000..69ae4c291
--- /dev/null
+++ b/app/models/ec_year.rb
@@ -0,0 +1,12 @@
+class EcYear < ApplicationRecord
+ belongs_to :ec_major_school
+
+ # 课堂配置
+ has_many :ec_courses, dependent: :destroy
+ has_many :ec_course_targets, through: :ec_courses
+ has_one :ec_training_objective, dependent: :destroy
+ has_many :ec_training_subitems, through: :ec_training_objective
+ has_many :ec_graduation_requirements, dependent: :destroy
+ has_many :ec_graduation_subitems, through: :ec_graduation_requirements
+ has_many :ec_year_students, dependent: :destroy
+end
diff --git a/app/models/ec_year_student.rb b/app/models/ec_year_student.rb
new file mode 100644
index 000000000..3d8b0b6df
--- /dev/null
+++ b/app/models/ec_year_student.rb
@@ -0,0 +1,7 @@
+class EcYearStudent < ApplicationRecord
+ belongs_to :ec_year
+
+ has_many :ec_student_achievements, dependent: :delete_all
+ has_many :ec_course_student_scores, dependent: :destroy
+ has_many :ec_student_score_targets, dependent: :delete_all
+end
diff --git a/app/models/edu_setting.rb b/app/models/edu_setting.rb
new file mode 100644
index 000000000..db6d545f0
--- /dev/null
+++ b/app/models/edu_setting.rb
@@ -0,0 +1,5 @@
+class EduSetting < ApplicationRecord
+ def self.get(key)
+ find_by_name(key.to_s)&.value
+ end
+end
diff --git a/app/models/evaluate_record.rb b/app/models/evaluate_record.rb
new file mode 100644
index 000000000..451409493
--- /dev/null
+++ b/app/models/evaluate_record.rb
@@ -0,0 +1,6 @@
+class EvaluateRecord < ApplicationRecord
+ default_scope { order("evaluate_records.id desc") }
+ belongs_to :game
+ belongs_to :shixun
+ belongs_to :user
+end
diff --git a/app/models/exercise.rb b/app/models/exercise.rb
new file mode 100644
index 000000000..e2623c692
--- /dev/null
+++ b/app/models/exercise.rb
@@ -0,0 +1,178 @@
+class Exercise < ApplicationRecord
+ belongs_to :course, counter_cache: true
+ belongs_to :exercise_bank, optional: true
+ belongs_to :user
+
+ has_many :exercise_users,:dependent => :destroy
+ has_many :exercise_questions,:dependent => :destroy
+ has_many :exercise_group_settings,:dependent => :destroy
+ has_many :tidings, as: :container
+ has_many :course_acts, class_name: 'CourseActivity', as: :course_act, dependent: :destroy
+
+ scope :is_exercise_published, -> { where("exercise_status > ? ",1)}
+ scope :unified_setting, -> { where("unified_setting = ?",true) }
+ scope :exercise_by_ids, lambda { |ids| where(id: ids) unless ids.blank? }
+ scope :exercise_by_status, lambda { |s| where(exercise_status: s) unless s.blank? }
+ scope :exercise_search, lambda { |keywords|
+ where("exercise_name LIKE ?", "%#{keywords}%") unless keywords.blank?}
+
+ validates :exercise_name, length: { maximum: 60, too_long: "60 characters is the maximum allowed" }
+
+ after_create :create_exercise_list
+
+ def create_exercise_list
+ str = ""
+ # TODO: 一次性为所有学生创建数据是否存在问题?
+ self.course.students.find_each do |student|
+ str += "," if str != ""
+ str += "(#{student.user_id}, #{self.id}, 0, '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
+ end
+
+ if str != ""
+ sql = "insert into exercise_users (user_id, exercise_id, commit_status, created_at, updated_at) values" + str
+ ActiveRecord::Base.connection.execute sql
+ end
+ end
+
+ #获取学生视角的试卷用户
+ def get_stu_exercise_users
+ if unified_setting #试卷统一设置
+ exercise_users
+ else
+ ex_group_setting_ids = exercise_group_settings.exercise_group_published.pluck(:course_group_id)
+ course_user_ids = course.students.where(course_group_id:ex_group_setting_ids).pluck(:user_id)
+ exercise_users.where(user_id:course_user_ids)
+ end
+ end
+
+ def get_exercise_end_time(user_id)
+ if unified_setting
+ self&.end_time
+ else
+ user_course_group = course.course_members.find_by(user_id: user_id)&.course_group_id
+ exercise_group_settings.find_by(course_group_id:user_course_group)&.end_time
+ end
+ end
+
+ #统一设置,为当前老师有权限的分班学生,分班设置,也为当前老师有权限的分班的学生
+ def all_exercise_users(user_id)
+ ex_users = self.exercise_users
+ group_ids = common_published_ids(user_id)
+ if group_ids.present?
+ ex_users = ex_users.where(user_id: course.students.where(course_group_id: group_ids).pluck(:user_id))
+ end
+ ex_users
+ end
+
+ #当前用户已发布的班级id和试卷分组已发布的班级id的交集
+ def common_published_ids(user_id)
+ current_user_groups = course.teacher_course_group_ids(user_id)
+ if unified_setting
+ if course.none_group_count > 0 #有未分班的,则发布到未发布分班
+ un_group_ids = [0]
+ else
+ un_group_ids = []
+ end
+ published_group_ids = (current_user_groups + un_group_ids).uniq #统一设置时,为当前用户的分班id及未分班
+ else
+ ex_group_setting = exercise_group_settings.pluck("course_group_id").uniq
+ common_all_ids = ex_group_setting & current_user_groups #当前用户有权限的已发布的分班id #非统一设置时,为当前用户有权限的且已发布分班的id
+ published_group_ids = common_all_ids.uniq
+ end
+ published_group_ids
+ end
+
+
+ #判断用户是否属于试卷分班的学生中
+ def check_user_in_course(user_id,user_identity)
+ ex_group_settings = exercise_group_settings.pluck(:course_group_id)
+ member = course.course_members.course_find_by_ids("user_id",user_id)
+ member_group_id = member.pluck(:course_group_id).uniq
+ if (member_group_id & ex_group_settings).size > 0 || user_identity < Course::STUDENT
+ true
+ else
+ false
+ end
+ end
+
+ #判断是否为分班,如果分班,试卷的截止时间为当前分班时间,否则为试卷的截止时间
+ def get_exercise_status(user_id)
+ user_group = course.course_members.find_by(user_id: user_id)
+ if user_group.present?
+ if user_group.role == "STUDENT" #为学生
+ is_teacher = false
+ else
+ is_teacher = true
+ end
+ ex_time = get_exercise_times(user_id,is_teacher)
+
+ pb_time = ex_time[:publish_time]
+ ed_time = ex_time[:end_time]
+
+ if pb_time.present? && ed_time.present? && pb_time <= Time.now && ed_time > Time.now
+ status = 2
+ elsif ed_time.present? && ed_time <= Time.now
+ status = 3
+ else
+ status = 1
+ end
+ else
+ status = exercise_status
+ end
+ status
+ end
+
+ #获取试卷的发布时间和截止时间。teacher 为boolean,当为true时,表示的是当前为老师
+ def get_exercise_times(user_id,teacher)
+ if unified_setting
+ pb_time = publish_time
+ en_time = end_time
+ else
+ ex_group_setting = exercise_group_settings
+ if teacher #当前为老师,为设置组的最大值和最小值
+ user_group = course.teacher_course_groups.get_user_groups(user_id)
+ user_group_ids = user_group.present? ? user_group.pluck(:course_group_id) : course.course_groups.pluck(:id)
+ user_ex_group_settings = ex_group_setting.find_in_exercise_group("course_group_id",user_group_ids)
+ pb_time_min = user_ex_group_settings.publish_time_no_null.map(&:publish_time)
+ en_time_max = user_ex_group_settings.end_time_no_null.map(&:end_time)
+ pb_time = pb_time_min.size > 0 ? pb_time_min.min : nil
+ en_time = en_time_max.size > 0 ? en_time_max.max : nil
+ else
+ user_group = course.students.course_find_by_ids("user_id",user_id)
+ if user_group.present?
+ user_group_id = user_group.first.course_group_id
+ user_ex_group_setting = ex_group_setting.find_in_exercise_group("course_group_id",user_group_id)
+ pb_time = user_ex_group_setting.present? ? user_ex_group_setting.first.publish_time : nil
+ en_time = user_ex_group_setting.present? ? user_ex_group_setting.first.end_time : nil
+ else
+ pb_time = nil
+ en_time = nil
+ end
+ end
+ end
+ {
+ "publish_time":pb_time,
+ "end_time":en_time
+ }
+ end
+
+ #判断当前用户的答题状态
+ def check_user_answer_status(user)
+ ex_answer_user = exercise_users.find_by(user_id: user.id)
+ user_ex_status = get_exercise_status(user.id)
+ user_status = 2
+ if ex_answer_user.present? && (ex_answer_user.start_at.present? || ex_answer_user.end_at.present?) #学生有过答题的,或者立即截止,但学生未做试卷的
+ user_status = ex_answer_user.commit_status
+ end
+ if ex_answer_user.present? && ex_answer_user.start_at.blank? && user_ex_status == 3
+ user_status = 4
+ end
+
+ user_status
+ end
+
+end
+
+
+
+
diff --git a/app/models/exercise_answer.rb b/app/models/exercise_answer.rb
new file mode 100644
index 000000000..465fa036b
--- /dev/null
+++ b/app/models/exercise_answer.rb
@@ -0,0 +1,14 @@
+class ExerciseAnswer < ApplicationRecord
+ #学生答题
+ belongs_to :user
+ belongs_to :exercise_question
+ belongs_to :exercise_choice, optional: true
+ has_many :exercise_answer_comments, :dependent => :destroy
+
+ scope :search_exercise_answer, lambda { |name,key| where("#{name} = ?",key)}
+ scope :search_answer_users, lambda {|name,ids| where("#{name}":ids)}
+ scope :exercise_no_full_scores, lambda { |score| where("score > 0.0 AND score < ?",score)}
+ scope :exercise_answer_is_right, -> {where("score > ?",0.0)} #判断答案是否正确,根据分数总和大于0
+ scope :score_reviewed, lambda {where("score >= ?",0.0)} #是否评分,用于判断主观题的
+
+end
\ No newline at end of file
diff --git a/app/models/exercise_answer_comment.rb b/app/models/exercise_answer_comment.rb
new file mode 100644
index 000000000..7da0ec4c7
--- /dev/null
+++ b/app/models/exercise_answer_comment.rb
@@ -0,0 +1,9 @@
+class ExerciseAnswerComment < ApplicationRecord
+ # belongs_to :exercise_answer
+ belongs_to :user
+ belongs_to :exercise_question
+ belongs_to :exercise_shixun_answer, optional: true
+ belongs_to :exercise_answer,optional: true
+
+ scope :search_answer_comments, lambda {|name,ids| where("#{name}":ids)}
+end
diff --git a/app/models/exercise_bank.rb b/app/models/exercise_bank.rb
new file mode 100644
index 000000000..22c2a5041
--- /dev/null
+++ b/app/models/exercise_bank.rb
@@ -0,0 +1,21 @@
+class ExerciseBank < ApplicationRecord
+ # container_type: Poll 问卷; Exercise试卷
+ belongs_to :user
+ belongs_to :course_list
+
+ has_many :exercise_bank_questions, :dependent => :destroy
+ has_many :polls, dependent: :nullify
+ has_many :exercises, dependent: :nullify
+
+ # def course_list_name
+ # self.course_list ? self.course_list.name : ""
+ # end
+ #
+ scope :myself, -> (user_id){where(user_id: user_id)}
+ scope :find_by_container, lambda { |id,type| where(container_id: id,container_type:type)}
+ scope :find_by_c_type, lambda { |keywords| where(container_type: keywords) unless keywords.blank?}
+ scope :public_exercises, -> {where(is_public: true)}
+ scope :exercise_bank_search, lambda { |keywords|
+ where("name LIKE ?", "%#{keywords}%") unless keywords.blank?}
+
+end
\ No newline at end of file
diff --git a/app/models/exercise_bank_choice.rb b/app/models/exercise_bank_choice.rb
new file mode 100644
index 000000000..d3a91bb02
--- /dev/null
+++ b/app/models/exercise_bank_choice.rb
@@ -0,0 +1,4 @@
+class ExerciseBankChoice < ApplicationRecord
+ belongs_to :exercise_bank_question
+ has_many :exercise_bank_standard_answers
+end
\ No newline at end of file
diff --git a/app/models/exercise_bank_question.rb b/app/models/exercise_bank_question.rb
new file mode 100644
index 000000000..5a39fd5d2
--- /dev/null
+++ b/app/models/exercise_bank_question.rb
@@ -0,0 +1,23 @@
+class ExerciseBankQuestion < ApplicationRecord
+ belongs_to :exercise_bank
+ belongs_to :shixun
+ has_many :exercise_bank_shixun_challenges,:dependent => :destroy
+ has_many :exercise_bank_choices, :dependent => :destroy
+ has_many :exercise_bank_standard_answers, :dependent => :destroy
+ #attr_accessible :question_number, :question_score, :question_title, :question_type
+
+ def question_type_name
+ case self.question_type
+ when 1
+ "单选题"
+ when 2
+ "多选题"
+ when 3
+ "填空题"
+ when 4
+ "简答题"
+ when 5
+ "实训题"
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/models/exercise_bank_shixun_challenge.rb b/app/models/exercise_bank_shixun_challenge.rb
new file mode 100644
index 000000000..2a9fafff3
--- /dev/null
+++ b/app/models/exercise_bank_shixun_challenge.rb
@@ -0,0 +1,7 @@
+class ExerciseBankShixunChallenge < ApplicationRecord
+ belongs_to :challenge
+ belongs_to :shixun
+ belongs_to :exercise_question
+ belongs_to :exercise_bank_question
+ # has_many :exercise_shixun_answers, :dependent => :destroy
+end
\ No newline at end of file
diff --git a/app/models/exercise_bank_standard_answer.rb b/app/models/exercise_bank_standard_answer.rb
new file mode 100644
index 000000000..9cd82afe6
--- /dev/null
+++ b/app/models/exercise_bank_standard_answer.rb
@@ -0,0 +1,5 @@
+class ExerciseBankStandardAnswer < ApplicationRecord
+ belongs_to :exercise_bank_question
+ belongs_to :exercise_bank_choice
+ #attr_accessible :answer_text
+end
\ No newline at end of file
diff --git a/app/models/exercise_choice.rb b/app/models/exercise_choice.rb
new file mode 100644
index 000000000..525251ce6
--- /dev/null
+++ b/app/models/exercise_choice.rb
@@ -0,0 +1,9 @@
+class ExerciseChoice < ApplicationRecord
+
+ belongs_to :exercise_question
+ has_many :exercise_answers, :dependent => :destroy
+ has_many :exercise_standard_answers, :dependent => :destroy
+
+ scope :find_choice_custom, lambda {|k,v| where("#{k} = ?",v)} #根据传入的参数查找问题
+ scope :left_choice_choose, lambda {|k,v| where("#{k} > ?",v)} #根据传入的参数查找问题
+end
\ No newline at end of file
diff --git a/app/models/exercise_group_setting.rb b/app/models/exercise_group_setting.rb
new file mode 100644
index 000000000..35af61af5
--- /dev/null
+++ b/app/models/exercise_group_setting.rb
@@ -0,0 +1,15 @@
+class ExerciseGroupSetting < ApplicationRecord
+ belongs_to :exercise,optional:true
+ belongs_to :course_group,optional:true
+ belongs_to :course,optional:true
+ # attr_accessible :end_time, :publish_time
+ # scope :find_by_exercise_course,lambda{|k,v| where("#{k} =?",v)}
+ scope :end_time_no_null, -> { where("end_time is NOT NULL")}
+ scope :publish_time_no_null, -> { where("publish_time is NOT NULL")}
+ scope :exercise_group_not_published, -> {where("publish_time is NULL OR publish_time > ?",Time.now)}
+ scope :exercise_group_published, -> {where("publish_time IS NOT NULL AND publish_time <= ?",Time.now)}
+ scope :exercise_group_ended, -> {where("end_time is NOT NULL AND end_time <= ?",Time.now)}
+
+ scope :find_in_exercise_group, lambda { |name,ids| where("#{name}": ids)}
+
+end
\ No newline at end of file
diff --git a/app/models/exercise_question.rb b/app/models/exercise_question.rb
new file mode 100644
index 000000000..ae58a7592
--- /dev/null
+++ b/app/models/exercise_question.rb
@@ -0,0 +1,47 @@
+#encoding: utf-8
+class ExerciseQuestion < ApplicationRecord
+
+ belongs_to :exercise
+ belongs_to :shixun, optional: true
+
+ has_many :exercise_choices, :dependent => :destroy
+ has_many :exercise_answers, :dependent => :destroy
+ has_many :exercise_shixun_challenges,:dependent => :destroy
+ has_many :exercise_shixun_answers, :dependent => :destroy
+ has_many :exercise_answer_comments, :dependent => :destroy
+ has_many :exercise_standard_answers, :dependent => :destroy
+
+ scope :insert_question_ex, lambda {|k| where("question_number > ?",k)}
+ scope :find_by_custom, lambda {|k,v| where("#{k} = ?",v)} #根据传入的参数查找问题
+ scope :left_question_choose, lambda {|k,v| where("#{k} > ?",v)} #根据传入的参数查找问题
+ scope :find_objective_questions, -> {where("question_type != ?",4)} #查找全部客观题
+ scope :next_exercise, lambda {|k| where("question_number > ?",k).first}
+ scope :last_exercise, lambda {|k| where("question_number < ?",k).last}
+
+ def question_type_name
+ case self.question_type
+ when 0
+ "单选题"
+ when 1
+ "多选题"
+ when 2
+ "判断题"
+ when 3
+ "填空题"
+ when 4
+ "简答题"
+ when 5
+ "实训题"
+ end
+ end
+
+ #获取问题的全部标准答案
+ def get_standard_answer_ids
+ exercise_standard_answers.pluck(:exercise_choice_id)
+ end
+
+ def get_standard_answer_text
+ exercise_standard_answers.pluck(:answer_text)
+ end
+
+end
diff --git a/app/models/exercise_shixun_answer.rb b/app/models/exercise_shixun_answer.rb
new file mode 100644
index 000000000..8548e497d
--- /dev/null
+++ b/app/models/exercise_shixun_answer.rb
@@ -0,0 +1,13 @@
+class ExerciseShixunAnswer < ApplicationRecord
+ belongs_to :exercise_question
+ belongs_to :user
+ belongs_to :exercise_shixun_challenge
+ has_many :exercise_answer_comments, :dependent => :destroy
+ # status 0: 未通过, 1:通过
+ # attr_accessible :answer_text, :score, :status
+ scope :search_shixun_answers, lambda {|name,ids| where("#{name}":ids)}
+ scope :search_shixun_keys, lambda { |name,key| where("#{name} = ?",key)}
+ scope :shixun_no_full_scores, lambda { |score| where("score > 0.0 AND score < ?",score)}
+ scope :score_reviewed, lambda {where("score is not NULL AND score >= ?",0.0)} #是否评分
+
+end
diff --git a/app/models/exercise_shixun_challenge.rb b/app/models/exercise_shixun_challenge.rb
new file mode 100644
index 000000000..c88e78ca7
--- /dev/null
+++ b/app/models/exercise_shixun_challenge.rb
@@ -0,0 +1,8 @@
+class ExerciseShixunChallenge < ApplicationRecord
+ belongs_to :challenge
+ belongs_to :shixun
+ belongs_to :exercise_question
+ has_many :exercise_shixun_answers, :dependent => :destroy
+
+ scope :cha_id_find, lambda {|id| where(id:id)}
+end
diff --git a/app/models/exercise_standard_answer.rb b/app/models/exercise_standard_answer.rb
new file mode 100644
index 000000000..b4c71bd76
--- /dev/null
+++ b/app/models/exercise_standard_answer.rb
@@ -0,0 +1,10 @@
+class ExerciseStandardAnswer < ApplicationRecord
+ #标准答案
+ belongs_to :exercise_question, optional:true
+ belongs_to :exercise_choice, optional:true
+
+ scope :find_standard_answer_custom, lambda {|k,v| where("#{k} = ?",v)} #根据传入的参数查找问题
+ scope :standard_by_ids, lambda { |s| where(exercise_choice_id: s) }
+
+
+end
diff --git a/app/models/exercise_user.rb b/app/models/exercise_user.rb
new file mode 100644
index 000000000..84f042b25
--- /dev/null
+++ b/app/models/exercise_user.rb
@@ -0,0 +1,12 @@
+class ExerciseUser < ApplicationRecord
+ belongs_to :user
+ belongs_to :exercise
+
+ scope :commit_exercise_by_status, lambda { |s| where(commit_status: s) }
+ scope :exercise_user_committed, -> {where("commit_status != ?",0) }
+ scope :current_exercise_user, lambda { |user_id,exercise_id| where(user_id: user_id,exercise_id:exercise_id)}
+ scope :exercise_commit_users, lambda {|user_ids| where(user_id:user_ids)}
+ scope :exercise_review, -> {where("subjective_score >= ?",0.0)} #已评阅,测试版的不一样是因为测试版的 主观题默认为0。0分,没有修改为-1分
+ scope :exercise_unreview, -> {where("subjective_score < ?",0.0)} #未评阅
+ scope :search_by_exercise, lambda {|ids| where(exercise_id:ids)} #根据试卷来查找
+end
diff --git a/app/models/experience.rb b/app/models/experience.rb
new file mode 100644
index 000000000..a5d828da3
--- /dev/null
+++ b/app/models/experience.rb
@@ -0,0 +1,3 @@
+class Experience < ApplicationRecord
+ belongs_to :user
+end
diff --git a/app/models/game.rb b/app/models/game.rb
new file mode 100644
index 000000000..c08884afb
--- /dev/null
+++ b/app/models/game.rb
@@ -0,0 +1,137 @@
+## 数据库字段说明
+# status 0:已开启;1:正在评测;2:通过评测;3:锁定
+# modify_time: 与challenges表的modify_time联合使用,2个字段一致,则标识测试集未修改,反之,被修改
+# answer_open: 查看查看答案的深度, 0: 未查看过答案, 其他数值与challenge_answer的level值相关
+# answer_deduction: 查看答案扣分的百分比;如 查看答案 扣除70%
+#
+class Game < ApplicationRecord
+ default_scope { order("games.created_at desc") }
+
+ has_many :outputs, -> { order('query_index DESC') }
+ has_many :challenge_samples, :dependent => :destroy
+ has_many :game_codes, :dependent => :destroy
+ has_many :evaluate_records, -> { order('id DESC') }, :dependent => :destroy
+ belongs_to :myshixun
+ belongs_to :user
+ belongs_to :challenge
+ has_one :run_code_message, :dependent => :destroy
+
+
+ #全部关卡数
+ scope :ch_games, lambda { |challenge_id| where(challenge_id:challenge_id) }
+ # 已通关的数量
+ scope :finished_num, -> (challenge_id) { where(:challenge_id => challenge_id, :status => 1)}
+ # 正在通关的数量
+ scope :doing_num, -> (challenge_id) { where(:challenge_id => challenge_id, :status => 0)}
+
+ #用户的全部关卡
+ scope :user_games, lambda { |user_id,challenge_id| where("user_id = ? AND challenge_id = ?",user_id,challenge_id) }
+
+ validates :identifier, uniqueness: true
+
+ # 根据得分比例来算实际得分(试卷、实训作业)
+ def real_score score
+ final_score == challenge.score ? score : (final_score.to_f / challenge.score) * score
+ end
+
+ # 判断实训是否全部通关
+ def had_done
+ Game.where(myshixun: self.myshixun_id).where("status != 2").count > 0 ? 0 : 1
+ end
+
+ def owner
+ self.user
+ end
+
+ def had_passed?
+ self.status == 2
+ end
+
+ # 因为outputs是按qurey_index倒序,所有first取的qurey_index的最大值
+ # 之所以加1是因为,存的时候要递增,如果仅取的话,则注意:必须减一
+ def query_index
+ # Output.unscope(:order).maximum("query_index").where(game_id: self.id)
+ self.outputs.unscope(:order).maximum("query_index")
+ end
+
+ # 用户在game关卡中获得的金币和经验值
+ # st: 0 实战类型;1 选择题类型
+ def user_get_gold_and_experience shixun_status, challenge
+ if challenge.st == 0 # 实战类型
+ if self.status == 2 # 通关了则取实际得分,没通关则取总分
+ gold = (shixun_status <= 1) ? 0 : self.final_score.to_i
+ # 只要过关了,查看了答案经验值就是0;通关前查看了答案金final_score为负数
+ experience = (shixun_status <= 1 || self.final_score.to_i < 0) ? 0 : challenge.score.to_i
+ else
+ gold = challenge.score.to_i
+ experience = gold
+ end
+ else
+ if self.status == 2
+ gold = (shixun_status <= 1) ? 0 : self.final_score.to_i
+ experience = (shixun_status <= 1 || self.final_score.to_i < 0) ? 0 : challenge.score.to_i
+ else
+ # 选择题只有在全对的时候才会获取final score总分,错任何一个题final_score就为0
+ gold = challenge.choose_score
+ experience = challenge.choose_score
+ end
+ end
+ [gold, experience]
+ end
+
+ # 上一关
+ def prev_of_current_game shixun_id, myshixun_id, challenge_position
+ Game.find_by_sql("select identifier from games where myshixun_id=#{myshixun_id}
+ and challenge_id=(select id from challenges where shixun_id=#{shixun_id} and
+ position=#{challenge_position - 1})").first.try(:identifier)
+ end
+
+ # 下一关
+ def next_of_current_game shixun_id, myshixun_id, challenge_position
+ Game.find_by_sql("select * from games where myshixun_id=#{myshixun_id}
+ and challenge_id=(select id from challenges where shixun_id=#{shixun_id} and
+ position=#{challenge_position + 1})").first.try(:identifier)
+ end
+
+ # 针对api形式取下一关identifier
+ def next_game shixun_id, myshixun_id, challenge_position
+ Game.find_by_sql("select * from games where myshixun_id=#{myshixun_id}
+ and challenge_id=(select id from challenges where shixun_id=#{shixun_id} and
+ position=#{challenge_position + 1})").first
+ end
+
+ # 因为outputs是按qurey_index倒序,所有first取的qurey_index的最大值
+ # 之所以加1是以为,存的时候要递增,如果仅取的话,则注意:必须减一
+ def next_query_index
+ if self.outputs.try(:first).present?
+ self.outputs.first.try(:query_index).to_i + 1
+ else
+ 1
+ end
+ end
+
+ def choose_correct_num query_index
+ self.outputs.where(query_index: query_index, result: 1).count
+ end
+
+ # 评测次数
+ def evaluate_count
+ self.outputs.pluck(:query_index).first
+ end
+
+ # 用户关卡得分
+ def get_user_final_score
+
+ end
+
+ # 按评测次数的查询
+ def distinct_query_index
+ self.outputs.group("outputs.query_index").reorder("query_index asc")
+ end
+
+ def lastest_code
+ self.game_codes.first.try(:new_code)
+ # game_code = GameCode.where(:game_id => self.id).first
+ # game_code.try(:new_code)
+ end
+end
diff --git a/app/models/game_code.rb b/app/models/game_code.rb
new file mode 100644
index 000000000..65181aa9a
--- /dev/null
+++ b/app/models/game_code.rb
@@ -0,0 +1,7 @@
+class GameCode < ApplicationRecord
+ belongs_to :game
+
+ # 按关卡路径搜索
+ scope :search_challenge_path, lambda { |cha_path| where("path = ?",cha_path) }
+
+end
diff --git a/app/models/grade.rb b/app/models/grade.rb
new file mode 100644
index 000000000..60a913e2e
--- /dev/null
+++ b/app/models/grade.rb
@@ -0,0 +1,11 @@
+class Grade < ApplicationRecord
+
+ belongs_to :user
+
+ has_many :tidings, :as => :container, :dependent => :destroy
+ after_create :send_tiding
+ def send_tiding
+ self.tidings << Tiding.new(:user_id => self.user_id, :trigger_user_id => 0, :parent_container_id => self.container_id,
+ :parent_container_type => self.container_type, :viewed => 0, :tiding_type => "System")
+ end
+end
diff --git a/app/models/graduation_group.rb b/app/models/graduation_group.rb
new file mode 100644
index 000000000..a7f3f227f
--- /dev/null
+++ b/app/models/graduation_group.rb
@@ -0,0 +1,7 @@
+class GraduationGroup < ApplicationRecord
+ belongs_to :course
+ has_many :course_members
+ belongs_to :user
+ has_one :graduation_task_group_assignation, dependent: :destroy
+ has_many :graduation_work_comment_assignations
+end
diff --git a/app/models/graduation_task.rb b/app/models/graduation_task.rb
new file mode 100644
index 000000000..308c51be4
--- /dev/null
+++ b/app/models/graduation_task.rb
@@ -0,0 +1,174 @@
+class GraduationTask < ApplicationRecord
+ # task_type 1: 普通作业 2:分组作业
+ # status 0:未发布 1:已发布 2:评阅中 3:交叉评阅
+ # comment_status 1:随机分配 2:指导老师手动分配 3:答辩组内老师互评 4:答辩组间老师互评
+
+ belongs_to :course, counter_cache: true
+ belongs_to :user
+
+ has_many :journals_for_messages, as: :jour, dependent: :destroy
+ has_many :praise_tread, as: :praise_tread_object, dependent: :destroy
+ has_many :course_acts, class_name: 'CourseActivity', as: :course_act, dependent: :destroy
+ has_many :tidings, as: :container, dependent: :destroy
+
+ has_many :attachments, as: :container, dependent: :destroy
+
+ has_many :graduation_task_group_assignations, dependent: :destroy
+ has_many :graduation_work_comment_assignations, dependent: :destroy
+
+ has_many :graduation_works, -> { where("is_delete != 1") }
+ has_many :graduation_work_scores
+
+ validates :name, length: { maximum: 60 }
+ validates :description, length: { maximum: 5000 }
+
+ # 未提交
+ scope :unfinished, -> {where(status: 0)}
+ # 按时提交
+ scope :finished, -> {where(status: 1)}
+ # 延迟提交
+ scope :delay_finished, -> {where(status: 2)}
+
+ #已发布的
+ scope :task_published, -> {where("publish_time <= ?",Time.now)}
+
+
+ def user_work user_id
+ work = self.graduation_works.find_by(user_id: user_id)
+ end
+
+ def task_type_name
+ task_type == 1 ? "普通作业" : "分组作业"
+ end
+
+ # 是否有学生提交过作品
+ def student_commit_works
+ graduation_works.has_committed.count > 0
+ end
+
+ # 是否有学生关联了项目
+ def student_relate_projects
+ task_type == 2 && graduation_works.where("project_id != 0").count > 0
+ end
+
+ # 是否发布
+ def published?
+ self.publish_time.present? && self.publish_time < Time.now
+ end
+
+ # 答辩组互评分配
+ def task_assign_group group_id
+ graduation_task_group_assignations.find_by(graduation_group_id: group_id)
+ end
+
+ def create_work_list
+ str = ""
+ self.course.students.each do |student|
+ str += "," if str != ""
+ str += "(#{self.id}, #{student.user_id}, #{self.course_id}, '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
+ end
+ if str != ""
+ sql = "insert into graduation_works (graduation_task_id, user_id, course_id, created_at, updated_at) values" + str
+ ActiveRecord::Base.connection.execute sql
+ end
+ end
+
+ #课程动态公共表记录
+ def act_as_course_activity
+ if self.course_acts.size == 0
+ self.course_acts << CourseActivity.new(user_id: self.user_id, course_id: self.course_id)
+ end
+ end
+
+ # 是否提交了作品
+ def has_commit_work user_id
+ work = self.graduation_works.find_by(user_id: user_id)
+ work.present? && work.work_status != 0
+ end
+
+ # 任务已评数 user_id: 对应老师的id
+ def comment_count(user_id)
+ course = self.course
+ # 如果有分班,则取此老师对应分班的已评数,没有分班,则取所有的
+ course_group_ids = course.group_course_power(user_id)
+ sql =
+ if course_group_ids.count > 0
+ %Q{
+ SELECT count(distinct graduation_work_id) cnt FROM graduation_work_scores WHERE reviewer_role IN(1,2) AND graduation_work_id
+ IN(SELECT id FROM graduation_works WHERE graduation_task_id = #{self.id} AND user_id
+ IN(SELECT user_id FROM course_members WHERE role = 4 AND course_group_id
+ IN(SELECT course_group_id FROM teacher_course_groups WHERE id IN(#{course_group_ids.join(",")}))
+ )
+ )
+ }
+ else
+ %Q{
+ SELECT COUNT(distinct graduation_work_id) cnt FROM graduation_works gw
+ JOIN graduation_work_scores gwc ON gw.graduation_task_id = gwc.graduation_task_id
+ WHERE reviewer_role IN(1,2) AND gwc.graduation_task_id = #{self.id}
+ }
+ end
+ GraduationWorkScore.find_by_sql(sql).first.try(:cnt).to_i
+ end
+
+ # 任务未评数
+ def uncomment_count(user_id)
+ user_ids = course.teacher_group_user_ids user_id
+ self.graduation_works.where(user_id: user_ids).count - self.comment_count(user_id)
+ end
+
+ # 任务未提交数
+ def unfinished_count user_id
+ course_group_ids = course.group_course_power(user_id)
+ if course_group_ids.count > 0
+ sql = %Q{
+ SELECT count(*) cnt FROM graduation_works gw WHERE work_status = 0 AND gw.graduation_task_id = #{self.id} AND gw.user_id
+ IN( SELECT user_id FROM course_members WHERE role = 4 AND course_group_id
+ IN( SELECT course_group_id FROM teacher_course_groups WHERE id not IN(#{course_group_ids.join(",")}) )
+ )
+ }
+ GraduationWork.find_by_sql(sql).first.try(:cnt)
+ else
+ self.graduation_works.where(work_status: 0).count
+ end
+ end
+
+ # 任务按时提交数
+ def finished_count(user_id)
+ course = self.course
+ course_group_ids = course.group_course_power(user_id)
+ if course_group_ids.count > 0
+ sql = %Q{
+ SELECT count(*) cnt FROM graduation_works gw WHERE work_status = 1 AND gw.graduation_task_id = #{self.id} AND gw.user_id
+ IN( SELECT user_id FROM course_members WHERE role = 4 AND course_group_id
+ IN( SELECT course_group_id FROM teacher_course_groups WHERE id IN(#{course_group_ids.join(",")}) )
+ )
+ }
+ GraduationWork.find_by_sql(sql).first.try(:cnt)
+ else
+ self.graduation_works.where(work_status: 1).count
+ end
+ end
+
+ def delay_finished_count(user_id)
+ course = self.course
+ course_group_ids = course.group_course_power(user_id)
+ if course_group_ids.count > 0
+ sql = %Q{
+ SELECT count(*) cnt FROM graduation_works gw WHERE work_status = 2 AND gw.graduation_task_id = #{self.id} AND gw.user_id
+ IN( SELECT user_id FROM course_members WHERE role = 4 AND course_group_id
+ IN( SELECT course_group_id FROM teacher_course_groups WHERE id IN(#{course_group_ids.join(",")}) )
+ )
+ }
+ GraduationWork.find_by_sql(sql).first.try(:cnt)
+ else
+ self.graduation_works.where(work_status: 2).count
+ end
+ end
+
+ # 是否具有分组
+ def have_grouping?
+ self.task_type == 2
+ end
+
+end
diff --git a/app/models/graduation_task_group_assignation.rb b/app/models/graduation_task_group_assignation.rb
new file mode 100644
index 000000000..52da65191
--- /dev/null
+++ b/app/models/graduation_task_group_assignation.rb
@@ -0,0 +1,6 @@
+class GraduationTaskGroupAssignation < ApplicationRecord
+ belongs_to :graduation_task
+ belongs_to :graduation_group
+ belongs_to :assign_group, class_name: 'GraduationGroup', foreign_key: :assign_graduation_group_id # 分配的互评组
+
+end
diff --git a/app/models/graduation_topic.rb b/app/models/graduation_topic.rb
new file mode 100644
index 000000000..3aed72d9c
--- /dev/null
+++ b/app/models/graduation_topic.rb
@@ -0,0 +1,72 @@
+# status 0:待选中 1:待确认 2:已确认
+class GraduationTopic < ApplicationRecord
+ belongs_to :course, counter_cache: true
+ belongs_to :user
+ has_many :student_graduation_topics, :dependent => :destroy
+ # 附件
+ #acts_as_attachable
+ has_many :attachments, as: :container, dependent: :destroy
+ # 回复
+ has_many :journals_for_messages, :as => :jour, :dependent => :destroy
+
+ # 题库
+
+
+ scope :search_by_name, ->(name) { where("graduation_topics.name LIKE ?", "%#{name}%")}
+ scope :search_by_status, ->(status) {where(status: status)}
+
+
+ #after_create :act_as_course_activity
+
+ # 课题名称和描述字段长度限制
+ validates :name, length: { maximum: 60,
+ too_long: "60 characters is the maximum allowed" }
+ validates :description, length: { maximum: 5000,
+ too_long: "5000 characters is the maximum allowed" }
+
+ def status_name
+ case self.status
+ when 0
+ "待选中"
+ when 1
+ "待确认"
+ when 2
+ "已确认"
+ else
+ "状态错误!"
+ end
+ end
+
+ #课程动态公共表记录
+ def act_as_course_activity
+ if self.course
+ if self.course_acts.size == 0
+ self.course_acts << CourseActivity.new(:user_id => self.user_id, :course_id => self.course_id)
+ end
+ end
+ end
+
+ # 跟选题后,查询所有选题的状态
+ def student_graduation_topic_status
+ sgt = self.student_graduation_topics
+ status =
+ if sgt.is_accepting.count == 0
+ 2
+ elsif sgt.is_refused.count == self.student_graduation_topics.size
+ 0
+ else
+ 1
+ end
+ return status
+ end
+
+ def teacher
+ User.find(self.tea_id)
+ end
+
+ # 用户选题的状态
+ def user_status(user_id)
+ self.student_graduation_topics.where(user_id: user_id).last.try(:status)
+ end
+
+end
diff --git a/app/models/graduation_work.rb b/app/models/graduation_work.rb
new file mode 100644
index 000000000..11255ce4e
--- /dev/null
+++ b/app/models/graduation_work.rb
@@ -0,0 +1,115 @@
+class GraduationWork < ApplicationRecord
+ # work_status: 0 未提交, 1 按时提交, 2 延时提交
+ belongs_to :user
+ belongs_to :course
+ belongs_to :project, optional: true
+ belongs_to :graduation_task, optional: true
+
+ belongs_to :commit_user, class_name: 'User', foreign_key: :commit_user_id, optional: true
+ has_many :attachments, as: :container, dependent: :destroy
+ has_many :tidings, as: :container, dependent: :destroy
+ has_many :graduation_work_scores, dependent: :destroy
+ has_many :graduation_work_comment_assignations, dependent: :destroy
+
+ validates :description, length: { maximum: 5000 }
+
+ scope :has_committed, lambda { where("work_status != 0") }
+
+ # 未提交
+ scope :unfinished, -> {where(work_status: 0)}
+ # 按时提交
+ scope :finished, -> {where(work_status: 1)}
+ # 延迟提交
+ scope :delay_finished, -> {where(work_status: 2)}
+
+ #根据graduation_task_id查找
+ scope :find_by_task, lambda{|ids| where(graduation_task_id: ids)}
+ scope :find_by_task_user, lambda{|ids| where(user_id: ids)}
+
+ before_save :set_work_score
+
+ def set_work_score
+ unless self.ultimate_score
+ if self.teacher_score.present? && self.cross_score.nil?
+ self.final_score = self.teacher_score
+ elsif self.cross_score.present? && self.teacher_score.nil?
+ self.final_score = self.cross_score
+ elsif self.teacher_score.present? && self.cross_score.present?
+ self.final_score = ((self.cross_score + self.teacher_score).to_f / 2.0).try(:round, 2)
+ end
+ if self.final_score
+ score = self.final_score - self.late_penalty
+ self.work_score = (score < 0 ? 0 : score).to_f.try(:round, 2) if score
+ else
+ self.work_score = nil
+ end
+ end
+ end
+
+ def delete_atta atta
+ last_score = graduation_work_scores.where.not(score: nil).last
+ atta.author_id == user_id && (!last_score.present? || last_score.try(:created_at) < atta.created_on)
+ end
+
+ # 分班名
+ def class_grouping_name
+ CourseMember.find_by(user_id: self.user_id, course_id: self.course_id).try(:course_group).try(:name) || '未分班'
+ end
+
+ # 分组名
+ def grouping_name
+ self.group_id == 0 ? "--" : "分组#{self.group_id}"
+ end
+
+ #用户是否有查看分数的权限
+ def check_score_power? current_user, course_identity
+ self.work_score.present? || course_identity < Course::STUDENT || self.user_id = current_user.id
+ end
+
+ # 作品是否能够分配指导老师
+ def assign_power?(course_identity)
+ course_identity < Course::STUDENT && self.graduation_task.cross_comment.present? && self.graduation_task.comment_status == 2
+ end
+
+ # 老师评阅分
+ def teacher_comment_score current_user, course_identity
+ # 为提交
+ if self.work_status == 0
+ "--"
+ else
+ # 未打分
+ if self.teacher_score.nil?
+ "未批阅"
+ else
+ # 是否有权限看
+ if self.check_score_power?(current_user, course_identity)
+ format("%.1f", self.teacher_score.round(1))
+ else
+ "**"
+ end
+ end
+ end
+ end
+
+ # 交叉评阅分
+ def cross_comment_score current_user, course_identity
+ if self.work_status == 0
+ "--"
+ else
+ if self.cross_score.nil?
+ "未批阅"
+ else
+ if self.check_score_power?(current_user, course_identity)
+ "#{format("%.1f", self.cross_score.round(1))}(#{self.graduation_work_scores
+ .where(reviewer_role: 2).group_by(&:user_id).count})"
+ else
+ "**"
+ end
+ end
+ end
+ end
+
+ def scored?
+ graduation_work_scores.where.not(reviewer_role: 3).exists?
+ end
+end
diff --git a/app/models/graduation_work_comment_assignation.rb b/app/models/graduation_work_comment_assignation.rb
new file mode 100644
index 000000000..33f30d2a0
--- /dev/null
+++ b/app/models/graduation_work_comment_assignation.rb
@@ -0,0 +1,8 @@
+class GraduationWorkCommentAssignation < ApplicationRecord
+ belongs_to :graduation_work
+ belongs_to :graduation_task
+ belongs_to :user
+ belongs_to :graduation_group
+
+ scope :myself, ->(user_id) {where(user_id: user_id)}
+end
diff --git a/app/models/graduation_work_score.rb b/app/models/graduation_work_score.rb
new file mode 100644
index 000000000..fa978b68f
--- /dev/null
+++ b/app/models/graduation_work_score.rb
@@ -0,0 +1,8 @@
+class GraduationWorkScore < ApplicationRecord
+ belongs_to :graduation_work
+ belongs_to :user
+ belongs_to :graduation_task
+ has_many :attachments, as: :container, dependent: :destroy
+
+ validates :comment, length: { maximum: 2000 }
+end
diff --git a/app/models/gtask_bank.rb b/app/models/gtask_bank.rb
new file mode 100644
index 000000000..55f83ef10
--- /dev/null
+++ b/app/models/gtask_bank.rb
@@ -0,0 +1,12 @@
+class GtaskBank < ApplicationRecord
+ belongs_to :user
+ belongs_to :graduation_task, optional: true
+ belongs_to :course_list, optional: true
+
+ has_many :attachments, as: :container, dependent: :destroy
+ has_many :graduation_tasks, dependent: :nullify
+
+ scope :myself, ->(user_id) { where(user_id: user_id)}
+ scope :is_public, -> { where(:is_public => true) }
+
+end
diff --git a/app/models/gtopic_bank.rb b/app/models/gtopic_bank.rb
new file mode 100644
index 000000000..af5b267a6
--- /dev/null
+++ b/app/models/gtopic_bank.rb
@@ -0,0 +1,10 @@
+class GtopicBank < ApplicationRecord
+ belongs_to :user
+ belongs_to :graduation_topic
+ belongs_to :course_list
+
+ has_many :attachments, as: :container, dependent: :destroy
+ has_many :graduation_topics, dependent: :nullify
+
+ scope :myself, ->(user_id) { where(user_id: user_id)}
+end
diff --git a/app/models/homework_bank.rb b/app/models/homework_bank.rb
new file mode 100644
index 000000000..7c9de0cda
--- /dev/null
+++ b/app/models/homework_bank.rb
@@ -0,0 +1,13 @@
+class HomeworkBank < ApplicationRecord
+ # homework_type: 1:普通作业 2:编程作业(弃用) 3:分组作业 4:实训作业
+ belongs_to :course_list
+ belongs_to :homework_common, optional: true
+ belongs_to :user
+
+ has_many :attachments, as: :container, :dependent => :destroy
+ has_many :homework_commons, dependent: :nullify
+
+ scope :is_public, -> { where(is_public: true)}
+ scope :myself, ->(user_id) { where(user_id: user_id)}
+
+end
diff --git a/app/models/homework_challenge_setting.rb b/app/models/homework_challenge_setting.rb
new file mode 100644
index 000000000..9aec5fb44
--- /dev/null
+++ b/app/models/homework_challenge_setting.rb
@@ -0,0 +1,3 @@
+class HomeworkChallengeSetting < ApplicationRecord
+ belongs_to :challenge
+end
diff --git a/app/models/homework_common.rb b/app/models/homework_common.rb
new file mode 100644
index 000000000..74d85e2ae
--- /dev/null
+++ b/app/models/homework_common.rb
@@ -0,0 +1,251 @@
+class HomeworkCommon < ApplicationRecord
+ # homework_type 1:普通作业 2:编程作业(弃用) 3:分组作业 4:实训作业
+ enum homework_type: { normal: 1, program: 2, group: 3, practice: 4 }, _suffix: true
+ has_many :homework_group_settings, dependent: :destroy
+ has_many :student_works, -> { where("is_delete != 1") }
+ has_one :homework_detail_manual, dependent: :destroy
+
+ # 分组作业的设置
+ has_one :homework_detail_group, dependent: :destroy
+
+ belongs_to :course, counter_cache: true
+ belongs_to :homework_bank, optional: true
+ has_many :homework_challenge_settings, dependent: :destroy
+
+ has_one :homework_commons_shixun, dependent: :destroy
+ has_and_belongs_to_many :shixuns, through: :homework_commons_shixun
+
+ # attachtype: 1(描述的附件), 2(参考答案的附件)
+ has_many :attachments, as: :container, dependent: :destroy
+
+ # 作业的评论
+ has_many :journals_for_messages, as: :jour, dependent: :destroy
+
+ # 二级目录
+ belongs_to :course_second_category, optional: true
+
+ # 课堂动态
+ has_many :course_acts, class_name: 'CourseActivity', as: :course_act, dependent: :destroy
+ has_many :tidings, as: :container, dependent: :destroy
+ # 实训作业的分班查重记录
+ has_many :homework_group_reviews, :dependent => :destroy
+ # 学生的查重情况
+ has_many :homework_review_results, :dependent => :destroy
+
+ validates :name, length: { maximum: 60 }
+ validates :description, length: { maximum: 5000 }
+ validates :reference_answer, length: { maximum: 5000 }
+
+ # after_update :update_activity
+ before_destroy :update_homework_bank_quotes
+
+ #1已发布的课堂作业
+ scope :homework_published, -> {where("homework_commons.publish_time IS NOT NULL AND homework_commons.publish_time <= ? ",Time.now)}
+ scope :published_no_end, -> {where("homework_commons.publish_time IS NOT NULL AND homework_commons.publish_time < ?
+ and homework_commons.end_time > ?", Time.now, Time.now)}
+ scope :search_homework_type, lambda {|num| where(homework_type:num)}
+ scope :unified_setting, -> {where("unified_setting = ? ", 1)}
+
+
+ # 是否显示参考答案
+ def view_answer identity, user_id
+ identity < Course::STUDENT || (identity == Course::STUDENT && answer_public &&
+ self.end_or_late && self.user_work(user_id).try(:work_status).to_i > 0)
+ end
+
+ # 申诉阶段
+ def appeal_duration
+ self.anonymous_appeal && [3, 4].include?(homework_detail_manual.comment_status)
+ end
+
+ # 作业对应的子目录/父目录名称
+ def category_info
+ case self.homework_type
+ when 'normal'
+ {category_id: course.common_course_modules.first.try(:id), category_name: course.common_course_modules.first.try(:module_name)}
+ when 'group'
+ {category_id: course.group_course_modules.first.try(:id), category_name: course.group_course_modules.first.try(:module_name)}
+ when 'practice'
+ if self.course_second_category.present?
+ {category_id: self.course_second_category.try(:id), category_name: self.course_second_category.try(:name)}
+ else
+ {category_id: course.shixun_course_modules.first.try(:id), category_name: course.shixun_course_modules.first.try(:module_name)}
+ end
+ end
+ end
+
+ # 根据是否统一发布获取作业的作品列表
+ def all_works
+ student_works = self.unified_setting ? self.student_works :
+ self.student_works.where(user_id: self.course.students.where(
+ course_group_id: self.homework_group_settings.group_published.pluck(:course_group_id)).
+ pluck(:user_id))
+ end
+
+ # 分班权限的老师可见的作品列表
+ def teacher_works user_id
+ member = course.course_member(user_id)
+ teacher_course_groups = member.try(:teacher_course_groups)
+ all_student_works = self.all_works
+ # 有分班权限的统计管理的分班且已发布的学生情况
+ if member.present? && teacher_course_groups.size > 0
+ group_ids = teacher_course_groups.pluck(:course_group_id)
+ all_student_works = all_student_works.where(user_id: course.students.where(course_group_id: group_ids).pluck(:user_id))
+ end
+ all_student_works
+ end
+
+ def user_work user_id
+ work = self.student_works.find_by_user_id(user_id)
+ end
+
+ # 是否在补交阶段内
+ def late_duration
+ homework_setting = self.homework_group_setting(User.current.id)
+ !course.is_end && self.publish_time && self.publish_time < Time.now && homework_setting.end_time &&
+ homework_setting.end_time < Time.now && self.allow_late && (self.late_time.nil? || self.late_time > Time.now)
+ end
+
+ # 作业是否补交截止或者不允许补交且提交截止
+ def end_or_late
+ status = false
+ if self.course.is_end || (self.allow_late && self.late_time && self.late_time < Time.now)
+ status = true
+ elsif !self.allow_late
+ homework_setting = self.homework_group_setting(User.current.id)
+ status = homework_setting.end_time && homework_setting.end_time < Time.now
+ end
+ status
+ end
+
+ # 作业是否可以查重
+ def code_review
+ self.homework_type == 'practice' && self.publish_time.present? && self.publish_time < Time.now && self.homework_group_reviews.count == 0
+ end
+
+ # 作业能否立即发布
+ def publish_immediately user
+ homework_detail_manual.try(:comment_status) == 0 || homework_group_settings.where(course_group_id: course.charge_group_ids(user)).
+ none_published.count > 0
+ end
+
+ # 作业能否立即截止
+ def end_immediately user
+ (unified_setting && homework_detail_manual.try(:comment_status) == 1 && end_time > Time.now) || homework_group_settings.
+ where(course_group_id: course.charge_group_ids(user)).published_no_end.count > 0
+ end
+
+ # 学生是否提交了作品
+ def user_has_commit_work user_id
+ work = self.student_works.find_by_user_id(user_id)
+ work.present? && work.work_status != 0
+ end
+
+ # 是否有学生提交过作品
+ def has_commit_work
+ student_works.has_committed.count > 0
+ end
+
+ # 是否有学生关联了项目
+ def has_relate_project
+ homework_type == "group" && student_works.where("project_id != 0").count > 0
+ end
+
+ # 作业描述的附件
+ def des_attachments
+ attachments.where(attachtype: 1)
+ end
+
+ # 参考答案的附件
+ def ref_attachments
+ attachments.where(attachtype: 2)
+ end
+
+ def update_activity
+ course_activity = CourseActivity.find_by_course_act_type_and_course_act_id(self.class.to_s, self.id)
+
+ course_activity.update_attributes(updated_at: Time.now) if course_activity
+ end
+
+ #删除时更新题库中的引用数
+ def update_homework_bank_quotes
+ old_banks = HomeworkBank.where(homework_common_id: self.id)
+ unless old_banks.blank?
+ old_banks.each do |bank|
+ bank.update_attributes(quotes: (bank.quotes - 1) > 0 ? (bank.quotes - 1) : 0, homework_common_id: nil)
+ end
+ end
+ end
+
+ # 查重是否有新结果
+ def code_reviews_new_results?
+ self.homework_group_reviews.where(status: 0).count > 0
+ end
+
+ # 任务已评数 user_id: 对应老师的id
+ def comment_count(user_id)
+ course = self.course
+ # 如果有分班,则取此老师对应分班的已评数,没有分班,则取所有的
+ course_group_ids = course.group_course_power(user_id)
+ sql =
+ if course_group_ids.count > 0
+ %Q{
+ SELECT count(distinct sw.id) cnt FROM student_works sw
+ JOIN student_works_scores sws on sws.student_work_id = sw.id WHERE
+ reviewer_role IN(1,2) AND sw.id IN (SELECT id FROM student_works WHERE homework_common_id = #{self.id}
+ AND user_id IN (SELECT user_id FROM course_members WHERE role = 4 AND course_group_id
+ IN (SELECT course_group_id FROM teacher_course_groups WHERE id IN(#{course_group_ids.join(",")}))
+ )
+ )
+ }
+ else
+ %Q{
+ SELECT COUNT(distinct sw.id) cnt FROM student_works sw
+ JOIN student_works_scores sws ON sw.id = sws.student_work_id
+ WHERE reviewer_role IN(1,2) AND sw.homework_common_id = #{self.id}
+ }
+ end
+ StudentWorksScore.find_by_sql(sql).first.try(:cnt).to_i
+ end
+
+ # 任务未评数
+ def uncomment_count(user_id)
+ user_ids = course.teacher_group_user_ids user_id
+ self.student_works.where(user_id: user_ids).count - self.comment_count(user_id)
+ end
+
+ # 作品未提交数
+ def unfinished_count user_id
+ self.teacher_works(user_id).unfinished.count
+ end
+
+ # 任务按时提交数
+ def finished_count user_id
+ self.teacher_works(user_id).finished.count
+ end
+
+ def delay_finished_count user_id
+ self.teacher_works(user_id).delay_finished.count
+ end
+
+ # 分组作业的最大分组id
+ def max_group_id
+ self.student_works.has_committed.maximum(:group_id).to_i + 1
+ end
+
+ # 作业的分班设置时间
+ def homework_group_setting user_id
+ member = course.course_member(user_id)
+ group_setting = self.homework_group_settings.find_by_course_group_id(member.try(:course_group_id))
+ homework_setting = group_setting.present? ? group_setting : self
+ homework_setting
+ end
+
+ def min_group_publish_time
+ HomeworkGroupSetting.where("homework_common_id = #{self.id} and publish_time is not null").pluck(:publish_time).min
+ end
+
+ def max_group_end_time
+ HomeworkGroupSetting.where("homework_common_id = #{self.id} and end_time is not null").pluck(:end_time).max
+ end
+end
diff --git a/app/models/homework_commons_shixun.rb b/app/models/homework_commons_shixun.rb
new file mode 100644
index 000000000..38df119ed
--- /dev/null
+++ b/app/models/homework_commons_shixun.rb
@@ -0,0 +1,4 @@
+class HomeworkCommonsShixun < ApplicationRecord
+ belongs_to :homework_common
+ belongs_to :shixun
+end
diff --git a/app/models/homework_detail_group.rb b/app/models/homework_detail_group.rb
new file mode 100644
index 000000000..ab0d13303
--- /dev/null
+++ b/app/models/homework_detail_group.rb
@@ -0,0 +1,3 @@
+class HomeworkDetailGroup < ApplicationRecord
+ belongs_to :homework_common
+end
diff --git a/app/models/homework_detail_manual.rb b/app/models/homework_detail_manual.rb
new file mode 100644
index 000000000..6bc8e3ce7
--- /dev/null
+++ b/app/models/homework_detail_manual.rb
@@ -0,0 +1,3 @@
+class HomeworkDetailManual < ApplicationRecord
+ belongs_to :homework_common
+end
diff --git a/app/models/homework_group_review.rb b/app/models/homework_group_review.rb
new file mode 100644
index 000000000..e0fef39a4
--- /dev/null
+++ b/app/models/homework_group_review.rb
@@ -0,0 +1,9 @@
+class HomeworkGroupReview < ApplicationRecord
+ belongs_to :homework_common
+ belongs_to :course_group
+ belongs_to :user
+ # status 0: 新建 1:已返回结果
+ # query_id 查询返回id 通过这个id取结果
+ # attr_accessible :status
+end
+
diff --git a/app/models/homework_group_setting.rb b/app/models/homework_group_setting.rb
new file mode 100644
index 000000000..ae9491cb3
--- /dev/null
+++ b/app/models/homework_group_setting.rb
@@ -0,0 +1,11 @@
+class HomeworkGroupSetting < ApplicationRecord
+ belongs_to :homework_common
+ belongs_to :course
+
+ scope :group_published, -> {where("homework_group_settings.publish_time IS NOT NULL AND homework_group_settings.publish_time <= ?", Time.now)}
+ scope :none_published, -> {where("homework_group_settings.publish_time IS NULL OR homework_group_settings.publish_time > ?", Time.now)}
+ scope :published_no_end, -> {where("homework_group_settings.publish_time IS NOT NULL AND homework_group_settings.publish_time < ?
+ and homework_group_settings.end_time > ?", Time.now, Time.now)}
+ scope :none_end, -> {where("homework_group_settings.end_time IS NOT NULL AND homework_group_settings.end_time > ?", Time.now)}
+
+end
diff --git a/app/models/homework_review_result.rb b/app/models/homework_review_result.rb
new file mode 100644
index 000000000..70c0afca3
--- /dev/null
+++ b/app/models/homework_review_result.rb
@@ -0,0 +1,4 @@
+class HomeworkReviewResult < ApplicationRecord
+ belongs_to :homework_common
+ belongs_to :user
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
new file mode 100644
index 000000000..e8f05202c
--- /dev/null
+++ b/app/models/issue.rb
@@ -0,0 +1,3 @@
+class Issue < ApplicationRecord
+ belongs_to :project
+end
\ No newline at end of file
diff --git a/app/models/journals_for_message.rb b/app/models/journals_for_message.rb
new file mode 100644
index 000000000..1da26fd22
--- /dev/null
+++ b/app/models/journals_for_message.rb
@@ -0,0 +1,50 @@
+class JournalsForMessage < ApplicationRecord
+ belongs_to :jour, :polymorphic => true
+ belongs_to :user
+ belongs_to :parent, class_name: "JournalsForMessage", foreign_key: "m_parent_id",
+ counter_cache: :m_reply_count, optional: true
+
+ has_many :praise_treads, as: :praise_tread_object, dependent: :destroy
+
+ #scope :children, -> {where(m_parent_id: self.id).includes(:user).reorder("created_on asc")}
+ #scope :children, -> (discuss_id){ where(parent_id: discuss_id).includes(:user).reorder("created_at asc") }
+
+ scope :parent_comment, -> { where(m_parent_id: nil)}
+ scope :search_by_jour_type, lambda{|type,ids| where(jour_type:type,jour_id: ids)}
+
+ # "jour_type", # 留言所属类型
+ # "jour_id", # 留言所属类型的id
+ # "notes", # 留言内容
+ # "reply_id", # 留言被回复留言者的用户id(用户a回复了用户b,这是b的id,用以查询谁给b留言了)
+ # "status", # 留言是否被查看(弃用)
+ # "user_id", # 留言者的id
+ # "m_parent_id", # 留言信息的父留言id
+ # "is_readed", # 留言是否已读
+ # "m_reply_count", # 留言的回复数量
+ # "m_reply_id" , # 回复某留言的留言id(a留言回复了b留言,这是b留言的id)
+ # "is_comprehensive_evaluation", # 1 教师评论、2 匿评、3 留言
+ # "hidden", 隐藏
+
+
+ # course_identity 课堂用户身份
+ def contents_show course_identity
+ if self.hidden && course_identity >= Course::STUDENT
+ nil
+ else
+ self.notes
+ end
+ end
+
+ def can_delete course_identity
+ course_identity < Course::STUDENT
+ end
+
+ def created_at
+ self.created_on
+ end
+
+ def children page, limit
+ JournalsForMessage.includes(:user).where(m_parent_id: self.id).page(page).per(limit).reorder("created_on asc")
+ end
+
+end
diff --git a/app/models/member.rb b/app/models/member.rb
new file mode 100644
index 000000000..d1feb8a37
--- /dev/null
+++ b/app/models/member.rb
@@ -0,0 +1,6 @@
+class Member < ApplicationRecord
+ has_many :member_roles, dependent: :destroy
+ belongs_to :course, optional: true
+ belongs_to :project, optional: true
+ belongs_to :user
+end
diff --git a/app/models/member_role.rb b/app/models/member_role.rb
new file mode 100644
index 000000000..900efc732
--- /dev/null
+++ b/app/models/member_role.rb
@@ -0,0 +1,3 @@
+class MemberRole < ApplicationRecord
+ belongs_to :member
+end
diff --git a/app/models/memo.rb b/app/models/memo.rb
new file mode 100644
index 000000000..524c37a96
--- /dev/null
+++ b/app/models/memo.rb
@@ -0,0 +1,49 @@
+class Memo < ApplicationRecord
+
+ has_many :memo_tag_repertoires, :dependent => :destroy
+ has_many :tag_repertoires, :through => :memo_tag_repertoires
+
+ has_many :praise_tread, as: :praise_tread_object, dependent: :destroy
+ has_one :praise_tread_cache, as: :object, dependent: :destroy
+
+ belongs_to :author, class_name: 'User', foreign_key: 'author_id'
+ belongs_to :parent, class_name: 'Memo', foreign_key: 'parent_id'
+
+ scope :field_for_list, lambda{
+ select([:id, :subject, :author_id, :sticky, :updated_at, :language, :reward, :all_replies_count, :viewed_count, :forum_id])
+ }
+ scope :user_posts, -> (user_id){ where(root_id: nil, author_id: user_id, forum_id: [3, 5]) }
+ scope :field_for_recommend, -> { select([:id, :subject, :language, :forum_id, :all_replies_count]) }
+ scope :memo_replies, -> (id) { where(root_id: id) }
+ scope :hot, -> { order("all_replies_count desc, updated_at desc") }
+ scope :posts, -> { where(root_id: nil, forum_id: [3, 5]) }
+
+ after_create :send_tiding
+
+ # 帖子的回复
+ def reply_for_memo
+ Memo.where(parent_id: id)
+ end
+
+ # 子回复
+ def children_of_reply
+ Memo.where(parent_id: id).includes(:author).reorder("created_at asc")
+ end
+
+ private
+
+ def send_tiding
+ tiding_attr = {
+ trigger_user_id: author_id, viewed: 0, tiding_type: 'Comment',
+ parent_container_type: 'Memo', belong_container_id: forum_id, belong_container_type: 'Forum'
+ }
+ if parent_id.present?
+ tiding_attr.merge!(user_id: parent.author_id, parent_container_id: root_id)
+ else
+ # 新帖子给超级管理员发消息
+ tiding_attr.merge!(user_id: 1, parent_container_id: id)
+ end
+
+ self.tidings << Tiding.new(tiding_attr)
+ end
+end
diff --git a/app/models/memo_tag_repertoire.rb b/app/models/memo_tag_repertoire.rb
new file mode 100644
index 000000000..99bb04f78
--- /dev/null
+++ b/app/models/memo_tag_repertoire.rb
@@ -0,0 +1,4 @@
+class MemoTagRepertoire < ApplicationRecord
+ belongs_to :memo
+ belongs_to :tag_repertoire
+end
diff --git a/app/models/message.rb b/app/models/message.rb
new file mode 100644
index 000000000..ae804e991
--- /dev/null
+++ b/app/models/message.rb
@@ -0,0 +1,87 @@
+class Message < ApplicationRecord
+ attr_accessor :total_replies_count
+
+ belongs_to :board, counter_cache: true
+ belongs_to :author, class_name: "User", foreign_key: 'author_id'
+ belongs_to :parent, class_name: "Message", foreign_key: "parent_id", counter_cache: :replies_count, optional: true
+ has_one :message_detail, dependent: :destroy
+ accepts_nested_attributes_for :message_detail, update_only: true
+
+ has_many :children, -> { order(updated_on: :desc ) }, class_name: "Message", foreign_key: "parent_id", dependent: :destroy
+ has_many :praise_treads, as: :praise_tread_object, dependent: :destroy
+ has_many :tidings, as: :container, dependent: :destroy
+ has_many :attachments, as: :container, dependent: :destroy
+ has_many :course_acts, :class_name => 'CourseActivity',:as =>:course_act ,:dependent => :destroy # 课程动态
+
+ scope :root_nodes, -> { where("parent_id IS NULL") } #判断该信息是帖子还是回复。null为发布的帖子
+ scope :reply_nodes, -> { where("parent_id IS NOT NULL") }
+ scope :visible, -> { where(is_hidden: false)}
+ scope :by_user, ->(user) { visible if user.nil? || !user.admin? }
+ scope :preload_messages, -> { includes(:author, :message_detail) }
+ scope :short, -> { select(:id, :subject, :created_on, :replies_count, :visits, :sticky, :praises_count) }
+ scope :ordered, -> (opts={}) { reorder("created_on #{opts[:sort] == 1 ? 'asc': 'desc'}") }
+ scope :by_ids, lambda { |ids| where(id: ids) unless ids.blank? }
+ scope :find_by_boards, ->(ids) {where(board_id: ids)}
+ scope :by_keywords, lambda { |keywords|
+ where("subject LIKE ?", "%#{keywords.split(" ").join('|')}%") unless keywords.blank?
+ }
+
+
+ #转发表
+ # has_many :forwards, as: :from, dependent: :destroy
+
+ validates :subject, length: { maximum: 255 }
+
+ def update_content(content)
+ message_detail.update_attributes(content: content)
+ end
+
+ def copy_attachments_to_new_message(new_message, user)
+ attachments.each do |attach|
+ new_message.attachments << Attachment.new(attach.attributes.except("id").merge(
+ quotes: 0,
+ downloads: 0,
+ author_id: user.id,
+ created_on: Time.now
+ ))
+ end
+ end
+
+ def self.bulk_move_to_other_board(message_ids, to_board_id, author_id)
+ message_ids.each do |id|
+ message = Message.find id
+ message.update_attributes(board_id: to_board_id, author_id: author_id) if message.parent_id.nil? # TODO 暂时只支持跟节点移动
+ end
+ end
+
+ # 包含二级回复的总点赞数
+ def total_praises_count
+ praises_count + children.includes(:children).reduce(0) { |count, filed|
+ sub_sum_count = filed.children.reduce(0) { |sub_count, sub_filed| sub_count += sub_filed.praises_count }
+ count += filed.praises_count
+ sub_sum_count += count
+ }
+ end
+
+ # 包含二级回复数的总回复数
+ def total_replies_count
+ replies_count + children.includes(:children).reduce(0) { |count, child|
+ sub_sum_count = child.children.reduce(0) { |sub_count, sub_child| sub_count += sub_child.replies_count }
+ count += child.replies_count
+ sub_sum_count += count
+ }
+ end
+
+ def has_replies
+ children.present?
+ end
+
+ #
+ def by_user_with_visible(user)
+ user.nil? || !user.admin? ? children.visible.limit(5) : children.limit(5)
+ end
+
+ def update_visits
+ update_attributes(:visits => visits + 1)
+ end
+end
diff --git a/app/models/message_detail.rb b/app/models/message_detail.rb
new file mode 100644
index 000000000..945d875f5
--- /dev/null
+++ b/app/models/message_detail.rb
@@ -0,0 +1,4 @@
+class MessageDetail < ApplicationRecord
+ belongs_to :message, :touch => true
+
+end
diff --git a/app/models/mirror_repository.rb b/app/models/mirror_repository.rb
new file mode 100644
index 000000000..be26b5ad1
--- /dev/null
+++ b/app/models/mirror_repository.rb
@@ -0,0 +1,11 @@
+class MirrorRepository < ApplicationRecord
+ has_many :shixun_mirror_repositories, :dependent => :destroy
+ has_many :shixun, :through => :shixun_mirror_repositories
+ has_many :mirror_scripts, :dependent => :destroy
+
+
+
+ scope :published_mirror, -> { where(status: 1) }
+ scope :published_main_mirror, -> { published_mirror.where(main_type: 1) }
+ scope :published_small_mirror, -> { published_mirror.where(main_type: 0) }
+end
diff --git a/app/models/mirror_script.rb b/app/models/mirror_script.rb
new file mode 100644
index 000000000..f4cb60867
--- /dev/null
+++ b/app/models/mirror_script.rb
@@ -0,0 +1,4 @@
+class MirrorScript < ApplicationRecord
+ belongs_to :mirror_repository
+
+end
diff --git a/app/models/myshixun.rb b/app/models/myshixun.rb
new file mode 100644
index 000000000..089b416de
--- /dev/null
+++ b/app/models/myshixun.rb
@@ -0,0 +1,107 @@
+class Myshixun < ApplicationRecord
+ include ApplicationHelper
+ has_many :games, :dependent => :destroy
+ has_many :student_works
+ has_one :shixun_modify, :dependent => :destroy
+
+ belongs_to :user
+ belongs_to :shixun, counter_cache: true
+
+ validates_uniqueness_of :shixun_id, :scope => :user_id, :message => "shixun_id and user_id unique error"
+ scope :finished, lambda { where(status: 1) }
+ scope :search_myshixun_user, ->(user_id){where(user_id:user_id)}
+
+
+ def owner
+ self.user
+ rescue ActiveRecord::RecordNotFound
+ end
+
+ def output_times
+ games.sum(:evaluate_count)
+ end
+
+ def repo_path
+ "#{self.repo_name}.git"
+ end
+
+ def is_complete?
+ self.status == 1
+ end
+
+ # 判断TPM的代码是否被修改了
+ # 判断依据是看tpm的最新提交记录和tpi数据库中存储的commit_id是否一致
+ def repository_is_modified shixun_repo_path
+ myshixun_commit_id = self.commit_id
+ if myshixun_commit_id.blank?
+ myshixun_commit_id = GitService.commits(repo_path: self.repo_path).last["id"]
+ self.update_column(:commit_id, myshixun_commit_id)
+ end
+ shixun_commit_id = GitService.commits(repo_path: shixun_repo_path).first["id"]
+ Rails.logger.warn("###############shixun_commit_id is #{shixun_commit_id}")
+ Rails.logger.warn("###############myshixun_commit_id is #{self.commit_id}")
+ result = myshixun_commit_id != shixun_commit_id ? true :false
+ return result
+ end
+
+ def mirror_name
+ self.shixun.mirror_repositories.map(&:type_name).blank? ? "" : self.shixun.mirror_repositories.map(&:type_name)
+ end
+
+ def main_mirror
+ self.shixun.mirror_repositories.published_main_mirror.try(:first)
+ end
+
+ # 当前任务:一个实训中只可能一个未完成任务(status 0或1只会存在一条记录)
+ # status:0 可以测评的,正在测评的
+ # 如果都完成,则当前任务为最后一个任务
+ def current_task
+
+ current_game = self.games.select{|game| game.status == 1 || game.status == 0}.first
+ if current_game.blank?
+ if self.status == 1
+ logger.info("@3333333333344444444#{self.id}")
+ current_game = Game.find_by_sql("SELECT g.* FROM games g, challenges c where g.myshixun_id=#{self.id} and
+ g.challenge_id = c.id and g.status = 2 order by c.position desc").first
+ else
+ # 如果没开启过的,status都为3,所以应该进入第一关
+ current_game = Game.find_by_sql("SELECT g.* FROM games g, challenges c where g.myshixun_id=#{self.id} and
+ g.challenge_id = c.id and g.status = 3 order by c.position asc").first
+ end
+ end
+ return current_game
+ end
+
+
+ # 挑战至第几关(已完成关卡数+1)
+ def exec_count
+ gcount = self.games.select{|game| game.status == 2}.size
+ gcount = gcount < self.games.size ? (gcount + 1) : gcount
+ end
+
+ # 个人实训得分
+ def total_score
+ self.games.where("status = 2 and final_score > 0").sum(:final_score).to_i
+ end
+
+ # 个人通关数
+ def passed_count
+ self.games.where(status: 2).count
+ end
+
+ # 通关时间
+ def passed_time
+ self.status == 1 ? self.games.map(&:end_time).max : "--"
+ end
+
+ # 耗时
+ def total_spend_time
+ game_spend_time self.games.where(status: 2).sum(:cost_time).to_i
+ end
+
+ # 通关总耗时
+ def total_cost_time
+ self.games.where(status: 2).sum(:cost_time).to_i
+ end
+
+end
diff --git a/app/models/onclick_time.rb b/app/models/onclick_time.rb
new file mode 100644
index 000000000..588da23f5
--- /dev/null
+++ b/app/models/onclick_time.rb
@@ -0,0 +1,4 @@
+class OnclickTime < ApplicationRecord
+ belongs_to :user
+
+end
diff --git a/app/models/output.rb b/app/models/output.rb
new file mode 100644
index 000000000..b813f6070
--- /dev/null
+++ b/app/models/output.rb
@@ -0,0 +1,3 @@
+class Output < ApplicationRecord
+ belongs_to :game
+end
diff --git a/app/models/platform_sample.rb b/app/models/platform_sample.rb
new file mode 100644
index 000000000..4d6cd21af
--- /dev/null
+++ b/app/models/platform_sample.rb
@@ -0,0 +1,5 @@
+class PlatformSample < ApplicationRecord
+ # samples_type: taskPass-实训过关任务; script-实训通用脚本模板;
+ # introduction-实训简介模板; knowledge-实训背景知识
+ validates_uniqueness_of :samples_type
+end
diff --git a/app/models/poll.rb b/app/models/poll.rb
new file mode 100644
index 000000000..7ebcceafd
--- /dev/null
+++ b/app/models/poll.rb
@@ -0,0 +1,156 @@
+class Poll < ApplicationRecord
+ belongs_to :course, counter_cache: true
+ belongs_to :user
+ belongs_to :exercise_bank, optional: true
+
+ # belongs_to :exercise_bank
+ has_many :poll_questions,dependent: :destroy
+ has_many :poll_users, :dependent => :destroy
+ has_many :users, :through => :poll_users #该文件被哪些用户提交答案过
+ has_many :poll_group_settings, :dependent => :destroy
+ has_many :course_acts, class_name: 'CourseActivity', as: :course_act, dependent: :destroy
+
+ has_many :tidings, as: :container, dependent: :destroy
+
+ scope :publish_or_not, -> { where("polls_status > ? ",1)}
+ scope :only_public, -> {where("is_public = ?",true)}
+ scope :public_or_unset, -> { where("unified_setting = ?",true) }
+ scope :poll_by_ids, lambda { |ids| where(id: ids) unless ids.blank? }
+ scope :poll_by_status, lambda { |s| where(polls_status: s) unless s.blank? }
+ scope :poll_group_ended, -> {where("end_time is NOT NULL AND end_time <= ?",Time.now)}
+
+ scope :poll_search, lambda { |keywords|
+ where("polls_name LIKE ?", "%#{keywords}%") unless keywords.blank?}
+
+ validates :polls_name, length: { maximum: 60, too_long: "60 characters is the maximum allowed" }
+
+ after_create :create_polls_list
+
+ def create_polls_list
+ str = ""
+ self.course.students.find_each do |student|
+ str += "," if str != ""
+ str += "(#{student.user_id},#{self.id}, 0, '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
+ end
+
+ if str != ""
+ sql = "insert into poll_users (user_id, poll_id, commit_status, created_at, updated_at) values" + str
+ ActiveRecord::Base.connection.execute sql
+ end
+ end
+
+ #获取学生视角的试卷用户
+ def get_poll_exercise_users
+ if unified_setting #试卷统一设置
+ poll_users
+ else
+ ex_group_setting_ids = poll_group_settings.poll_group_published.pluck(:course_group_id)
+ course_user_ids = course.students.where(course_group_id:ex_group_setting_ids).pluck(:user_id)
+ poll_users.where(user_id:course_user_ids)
+ end
+ end
+
+ # 统一设置,为当前老师有权限的分班学生,分班设置,也为当前老师有权限的分班的学生
+ def all_poll_users(user_id)
+ poll_users = self.poll_users
+ group_ids = poll_published_ids(user_id)
+ logger.info("##########_________group_ids______#########################{group_ids}")
+ if group_ids.present?
+ poll_users = poll_users.where(user_id: course.students.where(course_group_id: group_ids).pluck(:user_id))
+ end
+ poll_users
+ end
+
+ #当前用户已发布的班级id和试卷分组已发布的班级id的交集
+ def poll_published_ids(user_id)
+ current_user_groups = course.teacher_course_group_ids(user_id)
+ logger.info("##########_________current_user_groups______#########################{current_user_groups}")
+ if unified_setting
+ if course.none_group_count > 0 #有未分班的,则发布到未发布
+ un_group_ids = [0]
+ else
+ un_group_ids = []
+ end
+ (current_user_groups + un_group_ids).uniq #统一设置时,为当前用户的分班id
+ else
+ ex_group_setting = poll_group_settings.pluck("course_group_id").uniq
+ ex_group_setting & current_user_groups #当前用户有权限的已发布的分班id #非统一设置时,为当前用户有权限的且已发布分班的id
+ end
+ end
+
+ def get_poll_status(user_id)
+ user_group = course.course_members.find_by(user_id: user_id, is_active: 1)
+ if user_group.present?
+ if user_group.role == "STUDENT" #为学生
+ is_teacher = false
+ else
+ is_teacher = true
+ end
+ ex_time = get_poll_times(user_id,is_teacher)
+ pb_time = ex_time[:publish_time]
+ ed_time = ex_time[:end_time]
+ if pb_time.present? && ed_time.present? && pb_time <= Time.now && ed_time > Time.now
+ status = 2
+ elsif ed_time.present? && ed_time <= Time.now
+ status = 3
+ else
+ status = 1
+ end
+ else
+ status = polls_status
+ end
+ status
+ end
+
+ #获取问卷的发布时间和截止时间。teacher 为boolean,当为true时,表示的是当前为老师
+ def get_poll_times(user_id,teacher)
+ if unified_setting
+ pb_time = publish_time
+ en_time = end_time
+ else
+ poll_group_setting = poll_group_settings
+ if teacher #当前为老师,为设置组的最大值和最小值
+ user_group = course.teacher_course_groups.get_user_groups(user_id)
+ user_group_ids = user_group.present? ? user_group.pluck(:course_group_id) : course.course_groups.pluck(:id)
+ user_poll_group_settings = poll_group_setting.find_in_poll_group("course_group_id",user_group_ids)
+ pb_time_min = user_poll_group_settings.publish_time_present.map(&:publish_time)
+ en_time_max = user_poll_group_settings.end_time_present.map(&:end_time)
+ pb_time = pb_time_min.size > 0 ? pb_time_min.min : nil
+ en_time = en_time_max.size > 0 ? en_time_max.max : nil
+ else
+ user_group = course.students.find_by(user_id: user_id)
+ if user_group.present?
+ user_group_id = user_group.course_group_id
+ user_p_group_setting = poll_group_setting.find_by(course_group_id: user_group_id)
+ pb_time = user_p_group_setting.present? ? user_p_group_setting.publish_time : nil
+ en_time = user_p_group_setting.present? ? user_p_group_setting.end_time : nil
+ else
+ pb_time = nil
+ en_time = nil
+ end
+ end
+ end
+ {
+ "publish_time":pb_time,
+ "end_time":en_time
+ }
+ end
+
+ #判断当前用户的答题状态
+ def check_user_votes_status(user)
+ poll_answer_user = poll_users.find_by(user_id: user.id)
+ user_poll_status = get_poll_status(user.id)
+ user_status = 2
+
+ if poll_answer_user.present? && (poll_answer_user.start_at.present? || poll_answer_user.end_at.present?) #学生有过答题的,或者立即截止,但学生未做试卷的
+ user_status = poll_answer_user.commit_status
+ end
+
+ if poll_answer_user.present? && poll_answer_user.start_at.blank? && user_poll_status == 3
+ user_status = 4
+ end
+
+ user_status
+ end
+
+end
diff --git a/app/models/poll_answer.rb b/app/models/poll_answer.rb
new file mode 100644
index 000000000..423198bbf
--- /dev/null
+++ b/app/models/poll_answer.rb
@@ -0,0 +1,11 @@
+class PollAnswer < ApplicationRecord
+ # default_scope :order => 'answer_position'
+ # include Redmine::SafeAttributes
+
+ belongs_to :poll_question
+ has_many :poll_votes, :dependent => :destroy
+
+ scope :find_answer_by_custom, lambda {|k,v| where("#{k}":v)} #根据传入的参数查找问题
+ scope :left_answer_choose, lambda {|k,v| where("#{k} > ?",v)} #根据传入的参数查找问题
+
+end
\ No newline at end of file
diff --git a/app/models/poll_group_setting.rb b/app/models/poll_group_setting.rb
new file mode 100644
index 000000000..159b4a9c0
--- /dev/null
+++ b/app/models/poll_group_setting.rb
@@ -0,0 +1,15 @@
+class PollGroupSetting < ApplicationRecord
+ belongs_to :poll
+ belongs_to :course_group
+ belongs_to :course
+
+ scope :group_settings_by_ids, lambda { |ids| where(id: ids) unless ids.blank? }
+ scope :find_in_poll_group, lambda { |name,ids| where("#{name}": ids)}
+ scope :end_time_present, -> { where("end_time is not NULL")}
+ scope :publish_time_present, -> { where("publish_time is not NULL")}
+ scope :poll_group_published, -> {where("publish_time IS NOT NULL AND publish_time <= ?",Time.now)}
+ scope :poll_group_ended, -> {where("end_time is NOT NULL AND end_time <= ?",Time.now)}
+ scope :poll_group_not_published, -> {where("publish_time is NULL OR publish_time > ?",Time.now)}
+
+
+end
\ No newline at end of file
diff --git a/app/models/poll_question.rb b/app/models/poll_question.rb
new file mode 100644
index 000000000..958b69acd
--- /dev/null
+++ b/app/models/poll_question.rb
@@ -0,0 +1,26 @@
+class PollQuestion < ApplicationRecord
+
+ belongs_to :poll
+ has_many :poll_answers, :dependent => :destroy
+ attr_accessor :question_answers, :question_other_anser
+ has_many :poll_votes, :dependent => :destroy
+
+ scope :ques_count, lambda {|k| where("question_type = ?",k)}
+ scope :ques_necessary, -> {where("is_necessary = ?",1)}
+ scope :insert_question, lambda {|k| where("question_number > ?",k)}
+ scope :next_poll, lambda {|k| where("question_number > ?",k).first}
+ scope :last_poll, lambda {|k| where("question_number < ?",k).last}
+
+ def question_type_name
+ case self.question_type
+ when 1
+ "单选题"
+ when 2
+ "多选题"
+ when 3
+ "主观题"
+ when 4
+ "多行主观题"
+ end
+ end
+end
diff --git a/app/models/poll_user.rb b/app/models/poll_user.rb
new file mode 100644
index 000000000..3b17ef3b6
--- /dev/null
+++ b/app/models/poll_user.rb
@@ -0,0 +1,10 @@
+class PollUser < ApplicationRecord
+ belongs_to :poll
+ belongs_to :user
+
+ scope :current_poll_user, lambda { |user_id,poll_id| where(user_id: user_id,poll_id:poll_id)}
+ scope :commit_by_status, lambda { |s| where(commit_status: s)}
+ scope :find_by_group_ids, lambda { |ids| where(user_id: ids) }
+ scope :search_by_poll, lambda {|ids| where(poll_id:ids)} #根据问卷来查找
+end
+
diff --git a/app/models/poll_vote.rb b/app/models/poll_vote.rb
new file mode 100644
index 000000000..67b832890
--- /dev/null
+++ b/app/models/poll_vote.rb
@@ -0,0 +1,11 @@
+class PollVote < ApplicationRecord
+ # attr_accessible :poll_answers_id, :poll_questions_id, :user_id, :vote_text
+ # include Redmine::SafeAttributes
+
+ belongs_to :poll_answer, optional: true
+ belongs_to :poll_question
+ belongs_to :user
+
+ scope :find_current_vote,lambda {|k,v| where("#{k}": v)}
+ scope :find_vote_text,-> {where("vote_text IS NOT NULL")}
+end
\ No newline at end of file
diff --git a/app/models/portal_image.rb b/app/models/portal_image.rb
new file mode 100644
index 000000000..24b9af39d
--- /dev/null
+++ b/app/models/portal_image.rb
@@ -0,0 +1,2 @@
+class PortalImage < ApplicationRecord
+end
diff --git a/app/models/praise_tread.rb b/app/models/praise_tread.rb
new file mode 100644
index 000000000..58ec965b4
--- /dev/null
+++ b/app/models/praise_tread.rb
@@ -0,0 +1,29 @@
+class PraiseTread < ApplicationRecord
+ belongs_to :praise_tread_object, polymorphic: true, counter_cache: :praises_count
+ has_many :tidings, :as => :container, :dependent => :destroy
+
+ scope :liker, lambda { where(:praise_or_tread => 1) }
+ scope :user_liker, ->(user_id) { where(user_id: user_id, praise_or_tread: 1) }
+ scope :by_praise_tread_object_id_and_type, -> (object_id, object_type) { where(praise_tread_object_id: object_id, praise_tread_object_type: object_type)}
+
+ after_create :send_tiding
+
+ def send_tiding
+ case self.praise_tread_object_type
+ when "Memo","Message","Issue"
+ self.tidings << Tiding.new(:trigger_user_id => self.user_id, :user_id => self.praise_tread_object.author_id, :parent_container_id => self.praise_tread_object_id, :parent_container_type => self.praise_tread_object_type, :viewed => 0, :tiding_type => "Praise")
+ when "Discuss","Challenge","HomeworkCommon","JournalsForMessage","Journal","GraduationTopic","GraduationTask"
+ self.tidings << Tiding.new(:trigger_user_id => self.user_id, :user_id => self.praise_tread_object.user_id, :parent_container_id => self.praise_tread_object_id, :parent_container_type => self.praise_tread_object_type, :viewed => 0, :tiding_type => "Praise")
+ end
+ end
+
+ def self.find_object_by_type_and_id(id, type)
+ type.constantize find_by_id id
+ end
+
+ # 用户是否点赞
+ def self.user_praise?(user)
+ self.select{|pt| pt.user_id == user.id}.length > 0
+ end
+
+end
diff --git a/app/models/praise_tread_cache.rb b/app/models/praise_tread_cache.rb
new file mode 100644
index 000000000..5544d1854
--- /dev/null
+++ b/app/models/praise_tread_cache.rb
@@ -0,0 +1,2 @@
+class PraiseTreadCache < ApplicationRecord
+end
diff --git a/app/models/private_message.rb b/app/models/private_message.rb
new file mode 100644
index 000000000..1db4c9f66
--- /dev/null
+++ b/app/models/private_message.rb
@@ -0,0 +1,3 @@
+class PrivateMessage < ApplicationRecord
+ belongs_to :user
+end
diff --git a/app/models/project.rb b/app/models/project.rb
new file mode 100644
index 000000000..ddc6f6e5f
--- /dev/null
+++ b/app/models/project.rb
@@ -0,0 +1,20 @@
+class Project < ApplicationRecord
+ belongs_to :owner, class_name: 'User', foreign_key: :user_id
+ has_many :members
+ has_one :project_score, dependent: :destroy
+
+ has_many :issues
+
+ # 创建者
+ def creator
+ User.find(user_id).full_name
+ end
+
+ def project_members
+ self.members
+ end
+
+ def member?(user)
+ members.exists?(user_id: user.id)
+ end
+end
diff --git a/app/models/project_score.rb b/app/models/project_score.rb
new file mode 100644
index 000000000..51ed29a67
--- /dev/null
+++ b/app/models/project_score.rb
@@ -0,0 +1,29 @@
+class ProjectScore < ApplicationRecord
+ belongs_to :project
+
+ def all_score
+ self.issue_num * 4 + self.issue_journal_num + (self.changeset_num||0) * 4 + self.board_num * 2 +
+ self.board_message_num + self.attach_num * 5
+ end
+
+ # 代码提交得分
+ def code_score
+ (self.changeset_num||0) * 4
+ end
+
+ # issues得分
+ def issue_score
+ self.issue_num * 4 + self.issue_journal_num
+ end
+
+ # 资源得分
+ def attachment_score
+ self.attach_num * 5
+ end
+
+ # 帖子得分
+ def message_score
+ self.board_message_num
+ end
+
+end
diff --git a/app/models/question_bank.rb b/app/models/question_bank.rb
new file mode 100644
index 000000000..8b16b6b0f
--- /dev/null
+++ b/app/models/question_bank.rb
@@ -0,0 +1,10 @@
+class QuestionBank < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :course_list
+ #attr_accessible :container_id, :container_type, :quotes
+ scope :ques_by_container, lambda { |id,type| where(container_id: id,container_type:type)}
+
+ def bank_type
+ I18n.t("question_bank.container_type.#{container_type}", locale: 'zh-CN')
+ end
+end
\ No newline at end of file
diff --git a/app/models/relationship.rb b/app/models/relationship.rb
new file mode 100644
index 000000000..d5c754745
--- /dev/null
+++ b/app/models/relationship.rb
@@ -0,0 +1,6 @@
+class Relationship < ApplicationRecord
+ belongs_to :follower, class_name: "User"
+ belongs_to :followed, class_name: "User"
+ validates :follower_id, presence: true
+ validates :followed_id, presence: true
+end
diff --git a/app/models/repertoire.rb b/app/models/repertoire.rb
new file mode 100644
index 000000000..400cef494
--- /dev/null
+++ b/app/models/repertoire.rb
@@ -0,0 +1,6 @@
+class Repertoire < ApplicationRecord
+
+ has_many :sub_repertoires
+
+ has_many :tag_repertoires, through: :sub_repertoires
+end
diff --git a/app/models/run_code_message.rb b/app/models/run_code_message.rb
new file mode 100644
index 000000000..1fcac1cf7
--- /dev/null
+++ b/app/models/run_code_message.rb
@@ -0,0 +1,3 @@
+class RunCodeMessage < ApplicationRecord
+ belongs_to :game
+end
diff --git a/app/models/school.rb b/app/models/school.rb
new file mode 100644
index 000000000..af04ed0c7
--- /dev/null
+++ b/app/models/school.rb
@@ -0,0 +1,31 @@
+class School < ApplicationRecord
+ has_many :shixun_schools, :dependent => :destroy
+ has_many :shixuns, :through => :shixun_schools
+
+ has_many :ec_school_users, :dependent => :destroy
+ has_many :users, :through => :ec_school_users
+
+ has_many :ec_major_schools, :dependent => :destroy
+ has_many :ec_majors, :through => :ec_major_schools
+
+ # 学校管理员
+ def manager?(user)
+ ec_school_users.exists?(user_id: user.id)
+ end
+
+ # 专业管理员
+ def major_manager?(user)
+ relations = ec_major_schools.not_template.joins(:ec_major_school_users)
+ relations.exists?(ec_major_school_users: { user_id: user.id })
+ end
+
+ # 课程管理员
+ def course_manager?(user)
+ relations = ec_major_schools.not_template.joins(ec_years: :ec_course_users)
+ relations.exists?(ec_course_users: { user_id: user.id })
+ end
+
+ def manage_permission?(user)
+ manager?(user) || major_manager?(user) || course_manager?(user)
+ end
+end
diff --git a/app/models/shixun.rb b/app/models/shixun.rb
new file mode 100644
index 000000000..9ccefca92
--- /dev/null
+++ b/app/models/shixun.rb
@@ -0,0 +1,236 @@
+class Shixun < ApplicationRecord
+ # status: 0:编辑 1:申请发布 2:正式发布 3:关闭 -1:软删除
+ has_many :challenges, dependent: :destroy
+ has_many :myshixuns, :dependent => :destroy
+ has_many :shixun_members, dependent: :destroy
+ has_many :users, through: :shixun_members
+ has_many :discusses, as: :dis, dependent: :destroy
+ has_many :evaluate_records, dependent: :destroy
+ has_many :shixun_mirror_repositories
+ has_many :mirror_repositories, through: :shixun_mirror_repositories
+
+ has_many :shixun_schools, :dependent => :destroy
+ has_many :schools, :through => :shixun_schools
+
+ has_many :shixun_tag_repertoires, dependent: :destroy
+ has_many :tag_repertoires, through: :shixun_tag_repertoires
+ has_one :first_shixun_tag_repertoire, class_name: 'ShixunTagRepertoire'
+ has_one :first_tag_repertoire, through: :first_shixun_tag_repertoire, source: :tag_repertoire
+
+
+ #实训的关卡
+ has_many :exercise_shixun_challenges, :dependent => :destroy
+ has_many :exercise_bank_shixun_challenges, :dependent => :destroy
+
+ # 注意:这个地方是一张关联表,关联三张表(多对多关系)
+ has_many :stage_shixuns, dependent: :destroy
+ has_many :stages, through: :stage_shixuns
+ has_many :subjects, through: :stage_shixuns
+
+ has_one :shixun_info, dependent: :destroy
+
+ belongs_to :user
+
+
+ scope :search_by_name, ->(keyword) { where("name like ? or description like ? ",
+ "%#{keyword}%", "%#{keyword}%") }
+
+ #scope :include_user, ->(user_id) { where(id: Myshixun.where(user_id: user_id).pluck(:shixun_id))}
+
+ scope :filter_tag, ->(tag_level, tag_id) {
+ case tag_level
+ when 1 #大类
+ where(id: Repertoire.find(tag_id).tag_repertoires.joins(:shixun_tag_repertoires).pluck("shixun_tag_repertoires.shixun_id"))
+ when 2 #子类
+ where(id: SubRepertoire.find(tag_id).tag_repertoires.joins(:shixun_tag_repertoires).pluck("shixun_tag_repertoires.shixun_id"))
+ when 3 #tag
+ where(id: TagRepertoire.find(tag_id).shixun_tag_repertoires.pluck(:shixun_id))
+ end
+ }
+
+ scope :visible, -> { where("status != -1") }
+ scope :published, lambda{ where(status: 2) }
+ scope :unhidden, lambda{ where(hidden: 0, status: 2) }
+ scope :field_for_recommend, lambda{ select([:id, :name, :identifier, :myshixuns_count]) }
+ scope :find_by_ids,lambda{|k| where(id:k)}
+
+ # REDO:
+ def propaedeutics
+ shixun_info.try(:propaedeutics)
+ end
+
+ def description
+ shixun_info.try(:description)
+ end
+
+ def evaluate_script
+ shixun_info.try(:evaluate_script)
+ end
+
+ # 实训用户tag
+ def user_tags_name(user = User.current)
+ challenge_ids = challenges.pluck(:id)
+ user_challenge_ids = user.games.where(challenge_id: challenge_ids, status: 2).pluck(:challenge_id)
+ ChallengeTag.where(challenge_id: user_challenge_ids).pluck(:name).uniq
+ end
+
+ # 实训关卡tag
+ def challenge_tags_name
+ ChallengeTag.where(challenge_id: challenges.pluck(:id)).pluck(:name).uniq
+ end
+
+ def repo_path
+ "#{repo_name}.git"
+ end
+
+ # 实训对应的镜像主类别名(已选)
+ def main_mirror_name
+ mirror_repositories.published_main_mirror.first.try(:type_name) || ""
+ end
+
+ def main_mirror_id
+ mirror_repositories.published_main_mirror.first.try(:id) || -1
+ end
+
+ # 实训对应的镜像小类别名(已选)
+ def small_mirror_name
+ mirror_repositories.published_small_mirror.map(&:type_name)
+ end
+
+ def small_mirror_id
+ mirror_repositories.published_small_mirror.map(&:id)
+ end
+
+ # 实训镜像名
+ def mirror_name
+ names = mirror_repositories.map(&:type_name)
+ names.blank? ? '' : names
+ end
+
+ def script_tag
+ return unless mirror_script_id
+
+ MirrorScript.find_by_id(mirror_script_id)
+ end
+
+ def standrad_script
+ mirrors_id = mirror_repositories.map(&:id)
+ mirror_scripts = MirrorScript.where(mirror_repository_id: mirrors_id)
+ mirror_scripts.map { |ms| { scptname: ms.script_type, id: ms.id } }
+ end
+
+ def owner
+ User.find(self.user_id)
+ end
+
+ def is_published?
+ status > 1
+ end
+
+ # 当前用户开启的实训
+ def current_myshixun(user_id)
+ myshixuns.find_by(user_id: user_id)
+ end
+
+ # 实训技术平台
+ def show_shixun_mirror
+ mirror_repositories.map(&:type_name).join(';')
+ end
+
+ # 实训评分 cnt-评分次数 sum-总评分
+ def shixun_preference
+ game_star_info = Game.find_by_sql("select g.star from
+ (games g left join (myshixuns m join shixuns s on s.id = m.shixun_id) on m.id = g.myshixun_id)
+ where g.star != 0 and s.id = #{self.id}")
+ if game_star_info.present?
+ cnt = game_star_info.count
+ sum = game_star_info.sum(&:star).to_f
+ (sum / cnt.to_f).round(1)
+ else
+ 5.0
+ end
+ end
+
+ # 实训评分信息
+ # return [实训评分, 5星评分比例, 4星评分比例, 3星评分比例, 2星评分比例, 1星评分比例]
+ def shixun_preference_info
+ game_star_info = Game.find_by_sql("select g.star from
+ (games g left join (myshixuns m join shixuns s on s.id = m.shixun_id) on m.id = g.myshixun_id)
+ where g.star != 0 and s.id = #{self.id}")
+ star_info = []
+ if game_star_info.present?
+ 5.downto(1) do |i|
+ star_info << ((game_star_info.select{|s| s.star == i}.count / game_star_info.count.to_f) * 100).round
+ end
+ sum = star_info.sum
+ max = star_info.max
+ # 四舍五入引起5星比例超过100%, 将最大的比例的评分做出调整
+ if sum > 100
+ star_info = star_info.map{|s| s == max ? s - 1 : s}
+ elsif sum < 100
+ star_info = star_info.map{|s| s == max ? s + 1 : s}
+ end
+ cnt = game_star_info.count
+ sum = game_star_info.sum(&:star)
+ star_info.unshift((sum / cnt.to_f).round(1))
+ else
+ star_info = [5.0, 100, 0, 0, 0, 0]
+ end
+
+ star_info
+ end
+
+
+ # 实训关卡的总分(由于大部分是实践题,因此没关联查choose表)
+ # 提前加载问题:由于选择题比较少,所以几乎不会触发选择题的查询,所以没必要提前载入choose_score
+ def all_score
+ sum = 0
+ challenges.each do |challenge|
+ sum += challenge.st == 0 ? challenge.score : challenge.choose_score
+ end
+ sum
+ end
+
+ ### fork 数量
+ def fork_count
+ self.class.where(fork_from: id).count
+ end
+
+ # 学员等级
+ def shixun_trainee
+ case trainee
+ when 1 then '初级学员'
+ when 2 then '中级学员'
+ when 3 then '高级学员'
+ when 4 then '顶级学员'
+ else ''
+ end
+ end
+
+ def shixun_level
+ case trainee
+ when 1 then '初级'
+ when 2 then '中级'
+ when 3 then '高级'
+ when 4 then '顶级'
+ else ''
+ end
+ end
+
+ # 纯选择提类型
+ def is_choice_type?
+ challenges_count == challenges.where(st: 1).count
+ end
+
+ # 实训受用学校个数
+ def school_count
+ UserExtension.find_by_sql("select count(distinct ue.school_id) school_count from user_extensions ue right join myshixuns
+ on ue.user_id = myshixuns.user_id where myshixuns.shixun_id = #{self.id}").first.try(:school_count).to_i
+ end
+
+ def has_manager?(user)
+ return true if user.admin?
+
+ shixun_members.where(role: [1, 2]).exists?(user_id: user.id)
+ end
+end
diff --git a/app/models/shixun_info.rb b/app/models/shixun_info.rb
new file mode 100644
index 000000000..542e0222d
--- /dev/null
+++ b/app/models/shixun_info.rb
@@ -0,0 +1,5 @@
+class ShixunInfo < ApplicationRecord
+ belongs_to :shixun
+ validates_uniqueness_of :shixun_id
+ validates_presence_of :shixun_id
+end
diff --git a/app/models/shixun_member.rb b/app/models/shixun_member.rb
new file mode 100644
index 000000000..2b8b43171
--- /dev/null
+++ b/app/models/shixun_member.rb
@@ -0,0 +1,5 @@
+class ShixunMember < ApplicationRecord
+ belongs_to :shixun, counter_cache: :users_count
+ belongs_to :user
+
+end
diff --git a/app/models/shixun_mirror_repository.rb b/app/models/shixun_mirror_repository.rb
new file mode 100644
index 000000000..9376aac0b
--- /dev/null
+++ b/app/models/shixun_mirror_repository.rb
@@ -0,0 +1,5 @@
+class ShixunMirrorRepository < ApplicationRecord
+ belongs_to :shixun
+ belongs_to :mirror_repository
+ validates_uniqueness_of :shixun_id, :scope => :mirror_repository_id
+end
diff --git a/app/models/shixun_modify.rb b/app/models/shixun_modify.rb
new file mode 100644
index 000000000..a59f0ea13
--- /dev/null
+++ b/app/models/shixun_modify.rb
@@ -0,0 +1,4 @@
+class ShixunModify < ApplicationRecord
+ # attr_accessible :myshixun_id, :shixun_id, :status
+ belongs_to :myshixun
+end
diff --git a/app/models/shixun_school.rb b/app/models/shixun_school.rb
new file mode 100644
index 000000000..01e279eae
--- /dev/null
+++ b/app/models/shixun_school.rb
@@ -0,0 +1,6 @@
+class ShixunSchool < ApplicationRecord
+ belongs_to :shixun
+ belongs_to :school
+
+ scope :find_by_school, lambda { |k| where(school_id:k)}
+end
diff --git a/app/models/shixun_tag_repertoire.rb b/app/models/shixun_tag_repertoire.rb
new file mode 100644
index 000000000..6cb311f7a
--- /dev/null
+++ b/app/models/shixun_tag_repertoire.rb
@@ -0,0 +1,7 @@
+class ShixunTagRepertoire < ApplicationRecord
+ belongs_to :shixun
+ belongs_to :tag_repertoire
+
+ has_many :memos, :through => :memo_tag_repertoires
+ has_many :memo_tag_repertoires, :dependent => :destroy
+end
diff --git a/app/models/stage.rb b/app/models/stage.rb
new file mode 100644
index 000000000..7c80c4f9c
--- /dev/null
+++ b/app/models/stage.rb
@@ -0,0 +1,9 @@
+class Stage < ApplicationRecord
+ belongs_to :subject, counter_cache: true
+
+ has_many :stage_shixuns, -> { order("stage_shixuns.position ASC") }, dependent: :destroy
+ has_many :shixuns, :through => :stage_shixuns
+
+ validates :name, length: { maximum: 30 }
+ validates :description, length: { maximum: 300 }
+end
diff --git a/app/models/stage_shixun.rb b/app/models/stage_shixun.rb
new file mode 100644
index 000000000..eedb1f838
--- /dev/null
+++ b/app/models/stage_shixun.rb
@@ -0,0 +1,8 @@
+class StageShixun < ApplicationRecord
+ belongs_to :subject, counter_cache: :stages_count, counter_cache: :shixuns_count
+ # belongs_to :subject, counter_cache: :shixuns_count
+ belongs_to :shixun
+ belongs_to :stage, counter_cache: :shixuns_count
+end
+
+
diff --git a/app/models/student_graduation_topic.rb b/app/models/student_graduation_topic.rb
new file mode 100644
index 000000000..ea1774695
--- /dev/null
+++ b/app/models/student_graduation_topic.rb
@@ -0,0 +1,54 @@
+class StudentGraduationTopic < ApplicationRecord
+ # status 0:待确认 1:已同意 2:已拒绝
+ belongs_to :user
+ belongs_to :course
+ # belongs_to course_member, optional: true
+
+ belongs_to :graduation_topic
+ belongs_to :course_member
+
+ has_many :tidings, as: :container, dependent: :destroy
+
+ # 毕设确认数
+ scope :confirmation_count, ->{ where(:status => [1, 2]).count }
+
+ #用户的选题
+ scope :user_topics_accept, lambda{|user_id| where(user_id:user_id,status:[0,1])}
+ scope :is_refused, -> {where(status: 2)}
+ scope :is_accepted, -> {where(status: 1)}
+ scope :is_accepting, -> {where(status: 0)}
+
+
+ # 学生名称
+ def name
+ self.user.real_name
+ end
+
+ # 学生学号
+ def student_id
+ self.user.student_id
+ end
+
+ # 学生班级名称
+ def class_group_name
+ self.course_member.try(:course_group).try(:name)
+ end
+
+ # 学生选题时间
+ def selected_time
+ format_time self.created_at
+ end
+
+ # 学生选题状态名
+ def status_name
+ case self.status
+ when 0
+ "待确认"
+ when 1
+ "已同意"
+ when 2
+ "已拒绝"
+ end
+ end
+
+end
diff --git a/app/models/student_work.rb b/app/models/student_work.rb
new file mode 100644
index 000000000..0e674dd4c
--- /dev/null
+++ b/app/models/student_work.rb
@@ -0,0 +1,186 @@
+class StudentWork < ApplicationRecord
+ #学生提交作品表 #work_status :0 未提交 1 已提交 2 迟交
+ belongs_to :user
+ belongs_to :commit_user, class_name: 'User', foreign_key: :commit_user_id, optional: true
+ belongs_to :homework_common
+ belongs_to :myshixun, optional: true
+ has_many :student_works_evaluation_distributions, dependent: :destroy
+ has_many :student_works_scores, dependent: :destroy
+ belongs_to :project, optional: true
+
+ # attachtype: 1(正常提交的附件), 7(补交的附件)
+ has_many :attachments, as: :container, dependent: :destroy
+
+ has_many :tidings, as: :container, dependent: :destroy
+
+ has_many :challenge_work_scores, dependent: :destroy
+
+ before_save :set_work_score
+
+ validates :description, length: { maximum: 5000 }
+
+ scope :has_committed, lambda { where("work_status != 0") }
+ # 未提交
+ scope :unfinished, -> {where(work_status: 0)}
+ # 按时提交
+ scope :finished, -> {where(work_status: 1)}
+ # 延迟提交
+ scope :delay_finished, -> {where(work_status: 2)}
+
+ #按用户id查找
+ scope :homework_by_user, lambda{|ids| where(user_id: ids)}
+
+ #根据homework_common_id查找
+ scope :find_by_homework, lambda{|ids| where(homework_common_id: ids)}
+
+ def myshixun_consume
+ self.myshixun && self.myshixun.passed_count > 0 ? self.myshixun.total_spend_time : "--"
+ end
+
+ # 助教评分次数
+ def ta_comment_count
+ self.student_works_scores.where(reviewer_role: 2).group_by(&:user_id).count
+ end
+
+ # 匿评次数
+ def student_comment_num
+ homework_common.homework_detail_manual.comment_status > 2 ? self.student_works_scores.where(reviewer_role: 3).group_by(&:user_id).count : 0
+ end
+
+ # 匿评申诉总条数
+ def appeal_all_count
+ homework_common.homework_detail_manual.comment_status >= 3 ? self.student_works_scores.where("reviewer_role = 3 and appeal_status != 0").
+ group_by(&:user_id).count : 0
+ end
+
+ # 匿评申诉待处理条数
+ def appeal_deal_count
+ homework_common.homework_detail_manual.comment_status >= 3 ? self.student_works_scores.where("reviewer_role = 3 and appeal_status = 1").
+ group_by(&:user_id).count : 0
+ end
+
+ # 分组名
+ def work_group_name
+ self.group_id == 0 ? "--" : "分组#{self.group_id}"
+ end
+
+ # 助教评分
+ def ta_score ta_mode
+ if ta_mode == 1
+ ts_score = StudentWorksScore.find_by_sql("SELECT AVG(score) AS score FROM (SELECT * FROM
+ (SELECT * FROM student_works_scores WHERE student_work_id = #{self.id}
+ AND reviewer_role = 2 AND score IS NOT NULL ORDER BY created_at DESC)
+ AS t GROUP BY user_id) AS a")
+ score = ts_score.first.score.nil? ? nil : ts_score.first.score.to_f.try(:round, 2)
+ else
+ score = StudentWorksScore.where("student_work_id = #{self.id} AND reviewer_role = 2 AND score IS NOT NULL").last.try(:score)
+ end
+ end
+
+ # 缺评次数
+ def absence_count
+ absence_penalty_count = self.user.student_works_evaluation_distributions.joins(:student_work).
+ where("homework_common_id = #{self.homework_common_id}").count -
+ self.user.student_works_scores.joins(:student_work).where("homework_common_id = #{self.homework_common_id}
+ and reviewer_role = 3").select("distinct student_work_id").count
+ absence_penalty_count = absence_penalty_count < 0 ? 0 : absence_penalty_count
+ end
+
+ # 违规匿评次数
+ def appeal_count
+ appeal_count = self.user.student_works_scores.joins(:student_work).where("homework_common_id = #{self.homework_common_id}
+ and appeal_status = 3").select("distinct student_work_id").count
+ end
+
+ def delete_atta atta
+ last_score = student_works_scores.where.not(score: nil).last
+ atta.author_id == user_id && (!last_score.present? || last_score.try(:created_at) < atta.created_on)
+ end
+
+ # 作品总体评价
+ def overall_appraisal
+ case self.work_score.to_i
+ when (90..100)
+ '优秀'
+ when (70...90)
+ '良好'
+ when (60...70)
+ '及格'
+ when (0...60)
+ '不及格'
+ end
+ end
+
+ # 更新作品成绩
+ def set_work_score
+ if work_status > 0 && homework_common && homework_common.homework_detail_manual && !self.ultimate_score
+ case homework_common.homework_type
+ when "normal", "group"
+ if !homework_common.homework_detail_manual.final_mode
+ tea_ass_proportion = homework_common.homework_detail_manual.ta_proportion
+ tea_proportion = homework_common.homework_detail_manual.te_proportion
+ if self.teacher_score
+ if self.teaching_asistant_score.nil?
+ if self.student_score.nil?
+ self.final_score = self.teacher_score
+ else
+ te_proportion = tea_proportion + tea_ass_proportion / 2
+ final_te_score = BigDecimal.new("#{self.teacher_score}") * BigDecimal.new("#{te_proportion}")
+ final_s_score = BigDecimal.new("#{self.student_score}") * (BigDecimal.new('1.0') -
+ BigDecimal.new("#{te_proportion}"))
+ final_score = final_te_score + final_s_score
+ self.final_score = format("%.2f", final_score.to_f)
+ end
+ else
+ if self.student_score.nil?
+ te_proportion = tea_proportion + (1.0 - tea_proportion - tea_ass_proportion) / 2
+ final_te_score = BigDecimal.new("#{self.teacher_score}") * BigDecimal.new("#{te_proportion}")
+ final_ta_score = BigDecimal.new("#{self.teaching_asistant_score}") * (BigDecimal.new('1.0') -
+ BigDecimal.new("#{te_proportion}"))
+ final_score = final_te_score + final_ta_score
+ self.final_score = format("%.2f",final_score.to_f)
+ else
+ final_te_score = BigDecimal.new("#{self.teacher_score}") * BigDecimal.new("#{tea_proportion}")
+ final_ta_score = BigDecimal.new("#{self.teaching_asistant_score}") * BigDecimal.new("#{tea_ass_proportion}")
+ final_s_score = BigDecimal.new("#{self.student_score}") * (BigDecimal.new('1.0') -
+ BigDecimal.new("#{tea_proportion}") - BigDecimal.new("#{tea_ass_proportion}"))
+ final_score = final_te_score + final_ta_score + final_s_score
+ self.final_score = format("%.2f",final_score.to_f)
+ end
+ end
+ else
+ if self.teaching_asistant_score.nil?
+ self.final_score = self.student_score
+ elsif self.student_score.nil?
+ self.final_score = self.teaching_asistant_score
+ else
+ ta_proportion = tea_ass_proportion + tea_proportion / 2
+ final_ta_score = BigDecimal.new("#{self.teaching_asistant_score}") * BigDecimal.new("#{ta_proportion}")
+ final_s_score = BigDecimal.new("#{self.student_score}") * (BigDecimal.new('1.0') -
+ BigDecimal.new("#{ta_proportion}"))
+ final_score = final_ta_score + final_s_score
+ self.final_score = format("%.2f",final_score.to_f)
+ end
+ end
+
+ else
+ self.final_score = self.teacher_score ? self.teacher_score :
+ (self.teaching_asistant_score ? self.teaching_asistant_score : self.student_score)
+ end
+
+ # 作品最终得分work_score 等于作品得分 - 缺评扣分 - 迟交扣分 - 申诉扣分
+ score = self.final_score.to_f - self.absence_penalty - self.late_penalty - self.appeal_penalty if self.final_score
+ self.work_score = score ? format("%.2f",(score < 0 ? 0 : score).to_f) : nil
+
+ when "practice"
+ # 作品最终得分work_score 等于作品关卡得分 + 效率分 - 迟交扣分
+ work_score = self.final_score + self.eff_score - self.late_penalty if self.final_score
+ self.work_score = work_score ? format("%.2f", work_score < 0 ? 0 : work_score) : nil
+ end
+ end
+ end
+
+ def scored?
+ student_works_scores.where.not(reviewer_role: 3).exists?
+ end
+end
diff --git a/app/models/student_works_evaluation_distribution.rb b/app/models/student_works_evaluation_distribution.rb
new file mode 100644
index 000000000..7c3246e61
--- /dev/null
+++ b/app/models/student_works_evaluation_distribution.rb
@@ -0,0 +1,4 @@
+class StudentWorksEvaluationDistribution < ApplicationRecord
+ belongs_to :student_work
+ belongs_to :user
+end
diff --git a/app/models/student_works_score.rb b/app/models/student_works_score.rb
new file mode 100644
index 000000000..f6c7b3e33
--- /dev/null
+++ b/app/models/student_works_score.rb
@@ -0,0 +1,34 @@
+class StudentWorksScore < ApplicationRecord
+ #appeal_status: 0:正常;1:申诉中,2:撤销申诉;3:申诉成功;4:申诉被拒绝;5:申诉失效
+belongs_to :student_work
+ belongs_to :user
+ has_many :journals_for_messages, -> { order('created_on desc') }, as: :jour, dependent: :destroy
+ has_one :student_works_scores_appeal, dependent: :destroy
+ has_many :tidings, as: :container, dependent: :destroy
+ has_many :attachments, as: :container, dependent: :destroy
+
+ validates :comment, length: { maximum: 2000 }
+
+ def show_name identity, user
+ identity < Course::STUDENT || self.user == user || self.reviewer_role != 3
+ end
+
+ def allow_delete current_user, identity
+ self.is_invalid && (current_user == self.user || identity < Course::STUDENT) ||
+ (self.score.nil? && current_user == self.user)
+ end
+
+ # 匿评分
+ def stu_score work_id
+ StudentWorksScore.find_by_sql("SELECT AVG(score) AS score FROM student_works_scores WHERE
+ student_work_id = #{work_id} AND reviewer_role = 3 AND score IS NOT NULL
+ AND appeal_status != 3 AND is_invalid = 0").first.score.try(:round, 2).to_f
+ end
+
+ # 助教平均分
+ def ta_score work_id
+ StudentWorksScore.find_by_sql("SELECT AVG(score) AS score FROM student_works_scores WHERE
+ student_work_id = #{work_id} AND reviewer_role = 2 AND score IS NOT NULL
+ AND is_invalid = 0").first.score.try(:round, 2).to_f
+ end
+end
diff --git a/app/models/student_works_scores_appeal.rb b/app/models/student_works_scores_appeal.rb
new file mode 100644
index 000000000..9c9b33199
--- /dev/null
+++ b/app/models/student_works_scores_appeal.rb
@@ -0,0 +1,5 @@
+class StudentWorksScoresAppeal < ApplicationRecord
+ belongs_to :student_works_score
+ belongs_to :user
+ has_many :tidings, as: :container, dependent: :destroy
+end
diff --git a/app/models/students_for_course.rb b/app/models/students_for_course.rb
new file mode 100644
index 000000000..f0a8f1fbd
--- /dev/null
+++ b/app/models/students_for_course.rb
@@ -0,0 +1,4 @@
+class StudentsForCourse < ApplicationRecord
+ belongs_to :course, :class_name => 'Course', :foreign_key => :course_id
+ belongs_to :student, :class_name => 'User', :foreign_key => :student_id
+end
diff --git a/app/models/sub_repertoire.rb b/app/models/sub_repertoire.rb
new file mode 100644
index 000000000..d94db66c7
--- /dev/null
+++ b/app/models/sub_repertoire.rb
@@ -0,0 +1,3 @@
+class SubRepertoire < ApplicationRecord
+ has_many :tag_repertoires
+end
diff --git a/app/models/subject.rb b/app/models/subject.rb
new file mode 100644
index 000000000..ced610377
--- /dev/null
+++ b/app/models/subject.rb
@@ -0,0 +1,91 @@
+## 这个模块的统计有很大的性能问题
+# 可以在初始创建的时候
+
+class Subject < ApplicationRecord
+ #status :0 编辑中 1 审核中 2 发布
+ belongs_to :repertoire
+ belongs_to :user
+
+ has_many :stages, -> { order("stages.position ASC") }, dependent: :destroy
+
+ has_many :stage_shixuns, dependent: :destroy
+ has_many :shixuns, through: :stage_shixuns
+
+ has_many :subject_members, ->{ order("subject_members.position asc")}, dependent: :destroy
+ has_many :users, through: :subject_members
+ has_many :tidings, as: :container, dependent: :destroy
+ has_many :stages, -> { order("stages.position ASC") }, dependent: :destroy
+
+ validates :name, length: { maximum: 40 }
+ validates :description, length: { maximum: 5000 }
+ validates :learning_notes, length: { maximum: 500 }
+
+ scope :visible, lambda{where(status: 2)}
+ scope :published, lambda{where(status: 1)}
+ scope :unhidden, lambda{where(hidden: 0)}
+
+ after_create :send_tiding
+ def send_tiding
+ self.tidings << Tiding.new(user_id: self.user_id, trigger_user_id: self.user_id, belong_container_id: self.id, belong_container_type: 'Subject', tiding_type: "System", viewed: 0)
+ end
+
+ # 挑战过路径的成员数
+ def member_count
+ shixuns.sum(:myshixuns_count)
+ end
+
+ def all_score
+ subject_shixun_score + subject_shixun_choose_score
+ end
+
+ def subject_shixun_score
+ Challenge.find_by_sql("select sum(score) as shixun_count from challenges where st=0 and shixun_id in
+ (select id from shixuns where status > 1 and id in (SELECT distinct shixun_id
+ FROM `stage_shixuns` where subject_id=#{self.id}))").first.try(:shixun_count).to_i
+ end
+
+ def subject_shixun_choose_score
+ ChallengeChoose.find_by_sql("select sum(score) as choose_score from challenge_chooses where challenge_id in
+ (select distinct(id) from challenges where st !=0 and shixun_id in (select id from shixuns where status > 1 and id in
+ (SELECT distinct shixun_id FROM `stage_shixuns` where subject_id=#{self.id})))").first.try(:choose_score).to_i
+ end
+
+ def my_subject_score
+ shixuns_id = self.stage_shixuns.map(&:shixun_id)
+ shixuns_id = Shixun.where(:id => shixuns_id, :status =>[2, 3]).map(&:id)
+ shixuns_id = shixuns_id.present? ? shixuns_id.join(",") : -1
+ my_shixun_score = Challenge.find_by_sql("select sum(c.score) as score FROM challenges c join games g on g.challenge_id = c.id where g.status = 2 and g.user_id = #{User.current.id} and c.shixun_id in(#{shixuns_id})").first.try(:score)
+ my_choose_score = ChallengeChoose.find_by_sql("SELECT sum(g.final_score) score FROM (`challenge_chooses` cc join challenges c on cc.challenge_id = c.id) join games g on g.challenge_id = c.id where g.status = 2 and g.user_id = #{User.current.id} and c.shixun_id in(#{shixuns_id})").first.try(:score)
+ return my_shixun_score.to_i + my_choose_score.to_i
+ end
+
+ def subject_challenge_count
+ Challenge.find_by_sql("select count(*) as challenge_count from challenges where st=0 and shixun_id in(SELECT distinct shixun_id FROM `stage_shixuns` where subject_id=#{self.id})").first.try(:challenge_count).to_i
+ end
+
+ def subject_challenge_choose_count
+ Challenge.find_by_sql("select count(*) as challenge_choose_count from challenges where st!=0 and shixun_id in(SELECT distinct shixun_id FROM `stage_shixuns` where subject_id=#{self.id})").first.try(:challenge_choose_count).to_i
+ end
+
+ def my_subject_progress
+ shixun_id = self.stage_shixuns.map(&:shixun_id)
+ challenge_id = Game.where(:user_id => User.current.id, :status => 2).pluck(:challenge_id)
+ my_challenge_count = Challenge.where(:id => challenge_id, :shixun_id => shixun_id).count
+ count = self.subject_challenge_count == 0 ? 0 : ((my_challenge_count.to_f / self.subject_challenge_count).round(2) * 100).to_i
+ end
+
+ def my_consume_time
+ shixuns_id = self.stage_shixuns.map(&:shixun_id)
+ shixuns_id = shixuns_id.present? ? shixuns_id.join(",") : -1
+ my_shixun_time = Challenge.find_by_sql("select sum(g.cost_time) as score FROM challenges c join games g on g.challenge_id = c.id where g.status = 2 and g.user_id = #{User.current.id} and c.shixun_id in(#{shixuns_id})").first.try(:score)
+ return my_shixun_time.to_i
+ end
+
+ def member?(user)
+ subject_members.exists?(user_id: user.id)
+ end
+
+ def published?
+ status == 2
+ end
+end
\ No newline at end of file
diff --git a/app/models/subject_member.rb b/app/models/subject_member.rb
new file mode 100644
index 000000000..4bebb44f4
--- /dev/null
+++ b/app/models/subject_member.rb
@@ -0,0 +1,4 @@
+class SubjectMember < ApplicationRecord
+ belongs_to :subject
+ belongs_to :user
+end
diff --git a/app/models/tag_repertoire.rb b/app/models/tag_repertoire.rb
new file mode 100644
index 000000000..fe7054e23
--- /dev/null
+++ b/app/models/tag_repertoire.rb
@@ -0,0 +1,13 @@
+class TagRepertoire < ApplicationRecord
+ belongs_to :sub_repertoire
+
+ has_many :shixun_tag_repertoires, dependent: :destroy
+ has_many :shixuns, through: :shixun_tag_repertoires
+
+ has_many :memo_tag_repertoires, :dependent => :destroy
+ has_many :memos, :through => :memo_tag_repertoires
+
+
+ scope :field_for_list, lambda{select([:id, :name])}
+
+end
diff --git a/app/models/teacher_course_group.rb b/app/models/teacher_course_group.rb
new file mode 100644
index 000000000..fbf9849ac
--- /dev/null
+++ b/app/models/teacher_course_group.rb
@@ -0,0 +1,15 @@
+class TeacherCourseGroup < ApplicationRecord
+ belongs_to :course_group
+ belongs_to :course, optional: true
+ belongs_to :user
+
+ belongs_to :course_member, optional: true
+
+ scope :find_teacher_group_ids, lambda { |ids| where(course_group_id: ids) unless ids.blank?}
+ scope :get_user_groups,lambda {|ids| where(user_id:ids)}
+
+ def course_members
+ self.course_group.course_members
+ end
+
+end
diff --git a/app/models/tem_test.rb b/app/models/tem_test.rb
new file mode 100644
index 000000000..ca4a1b98f
--- /dev/null
+++ b/app/models/tem_test.rb
@@ -0,0 +1,2 @@
+class TemTest < ApplicationRecord
+end
diff --git a/app/models/test_set.rb b/app/models/test_set.rb
new file mode 100644
index 000000000..148cb8720
--- /dev/null
+++ b/app/models/test_set.rb
@@ -0,0 +1,2 @@
+class TestSet < ApplicationRecord
+end
diff --git a/app/models/tiding.rb b/app/models/tiding.rb
new file mode 100644
index 000000000..66b1f85be
--- /dev/null
+++ b/app/models/tiding.rb
@@ -0,0 +1,9 @@
+class Tiding < ApplicationRecord
+ belongs_to :user
+ belongs_to :trigger_user, class_name: 'User', optional: true
+ belongs_to :container, polymorphic: true, optional: true
+ belongs_to :parent_container, polymorphic: true, optional: true
+ belongs_to :belong_container, polymorphic: true, optional: true
+
+ has_many :attachments, as: :container
+end
\ No newline at end of file
diff --git a/app/models/token.rb b/app/models/token.rb
new file mode 100644
index 000000000..cd61090be
--- /dev/null
+++ b/app/models/token.rb
@@ -0,0 +1,103 @@
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+class Token < ActiveRecord::Base
+ belongs_to :user
+ validates_uniqueness_of :value
+
+ before_create :delete_previous_tokens, :generate_new_token
+
+ @@validity_time = 1.day
+
+ def generate_new_token
+ self.value = Token.generate_token_value
+ end
+
+ def self.get_or_create_permanent_login_token(user, type)
+ token = Token.get_token_from_user(user, type)
+ unless token
+ token = Token.create(:user => user, :action => type)
+ else
+ token.update_attribute(:created_on, Time.now)
+ end
+ token
+ end
+
+ def self.get_token_from_user(user, action)
+ token = Token.where(:action => action, :user_id => user).first
+ unless token
+ token = Token.create!(user_id: user.id, action: action)
+ end
+ token
+ end
+
+ # Return true if token has expired
+ def expired?
+ return Time.now > self.created_on + @@validity_time
+ end
+
+ # Delete all expired tokens
+ def self.destroy_expired
+ Token.delete_all ["action NOT IN (?) AND created_on < ?", ['feeds', 'api', 'autologin'], Time.now - @@validity_time]
+ end
+
+ # Returns the active user who owns the key for the given action
+ def self.find_active_user(action, key, validity_days=nil)
+ user = find_user(action, key, validity_days)
+ if user && user.active?
+ user
+ end
+ end
+
+ # Returns the user who owns the key for the given action
+ def self.find_user(action, key, validity_days=nil)
+ token = find_token(action, key, validity_days)
+ if token
+ token.user
+ end
+ end
+
+ # Returns the token for action and key with an optional
+ # validity duration (in number of days)
+ def self.find_token(action, key, validity_days=nil)
+ action = action.to_s
+ key = key.to_s
+ return nil unless action.present? && key =~ /\A[a-z0-9]+\z/i
+
+ token = Token.where(value: key, action: action).first
+ if token && (token.action == action) && (token.value == key) && token.user
+ if validity_days.nil? || (token.created_on > validity_days.days.ago)
+ token
+ end
+ end
+ end
+
+ def self.generate_token_value
+ Educoder::Utils.random_hex(20)
+ end
+
+ def self.delete_user_all_tokens(user)
+ Token.delete_all(user_id: user.id)
+ end
+
+ private
+
+ # Removes obsolete tokens (same user and action)
+ def delete_previous_tokens
+ if user
+ Token.where(['user_id = ? AND action = ?', user.id, action]).delete_all
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 000000000..60765b852
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,589 @@
+class User < ApplicationRecord
+ include Watchable
+ # Account statuses
+ STATUS_ANONYMOUS = 0
+ STATUS_ACTIVE = 1
+ STATUS_REGISTERED = 2
+ STATUS_LOCKED = 3
+
+ # tpi tpm权限控制
+ EDU_ADMIN = 1 # 超级管理员
+ EDU_BUSINESS = 2 # 运营人员
+ EDU_SHIXUN_MANAGER = 3 # 实训管理员
+ EDU_SHIXUN_MEMBER = 4 # 实训成员
+ EDU_CERTIFICATION_TEACHER = 5 # 平台认证的老师
+ EDU_GAME_MANAGER = 6 # TPI的创建者
+ EDU_TEACHER = 7 # 平台老师,但是未认证
+ EDU_NORMAL = 8 # 普通用户
+
+ VALID_EMAIL_REGEX = /^[a-zA-Z0-9]+([.\-_\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/i
+ VALID_PHONE_REGEX = /^1\d{10}$/
+
+ LOGIN_LENGTH_LIMIT = 30
+ MAIL_LENGTH_LMIT = 60
+
+ MIX_PASSWORD_LIMIT = 8
+
+ has_one :user_extension, dependent: :destroy
+ accepts_nested_attributes_for :user_extension, update_only: true
+
+ has_many :memos, foreign_key: 'author_id'
+ has_many :shixun_members, :dependent => :destroy
+ has_many :shixuns, :through => :shixun_members
+ has_many :myshixuns, :dependent => :destroy
+ has_many :study_shixuns, through: :myshixuns, source: :shixun # 已学习的实训
+ has_many :course_messages
+ has_many :courses, dependent: :destroy
+
+ #试卷
+ has_many :exercise_banks, :dependent => :destroy
+ has_many :exercise_users, :dependent => :destroy
+ has_many :exercise_answers, :dependent => :destroy #针对每个题目学生的答案
+ has_many :exercise_shixun_answers, :dependent => :destroy #针对每个实训题目学生的答案
+ has_many :exercise_answer_comments, :dependent => :destroy
+ has_many :exercises, :dependent => :destroy #创建的试卷
+
+ has_many :homework_banks, dependent: :destroy
+
+ has_many :graduation_works, dependent: :destroy
+
+ # 关注
+ has_many :relationships, foreign_key: "follower_id", dependent: :destroy
+ has_many :followed_users, through: :relationships, source: :followed
+ # 粉丝
+ has_many :reverse_relationships, foreign_key: "followed_id",
+ class_name: "Relationship",
+ dependent: :destroy
+ has_many :followers, through: :reverse_relationships, source: :follower
+ has_many :students_for_courses, foreign_key: :student_id, dependent: :destroy
+ has_one :onclick_time, :dependent => :destroy
+
+ # 新版私信
+ has_many :private_messages, :dependent => :destroy
+ has_many :tidings, :dependent => :destroy
+
+ has_many :games, :dependent => :destroy
+ has_many :subjects, :through => :subject_members
+ has_many :subject_members, :dependent => :destroy
+ has_many :grades, :dependent => :destroy
+ has_many :experiences, :dependent => :destroy
+ has_many :student_works, :dependent => :destroy
+ has_many :student_works_scores
+ has_many :student_works_evaluation_distributions
+
+ # 毕业设计
+ has_many :graduation_topics, :dependent => :destroy
+ has_many :student_graduation_topics, :dependent => :destroy
+
+ # 题库
+ has_many :question_banks, :dependent => :destroy
+ # 毕设任务题库
+ has_many :gtask_banks, dependent: :destroy
+ has_many :gtopic_banks, dependent: :destroy
+
+ #问卷
+ has_many :course_members, :dependent => :destroy
+ has_many :poll_votes, :dependent => :destroy
+ has_many :poll_users, :dependent => :destroy
+
+ has_many :messages,foreign_key: 'author_id',:dependent => :destroy
+
+ has_many :journals_for_messages, :as => :jour, :dependent => :destroy
+ has_many :teacher_course_groups, :dependent => :destroy
+
+ has_many :attachments,foreign_key: :author_id, :dependent => :destroy
+
+ # 工程认证
+ has_many :ec_school_users,:dependent => :destroy
+ has_many :schools, :through => :ec_school_users
+
+ has_many :ec_major_school_users, :dependent => :destroy
+ has_many :ec_major_schools, :through => :ec_major_school_users
+
+ has_many :ec_course_users
+
+ has_many :department_members, dependent: :destroy #部门管理员
+
+ # 课堂
+ has_many :student_course_members, -> { course_students }, class_name: 'CourseMember'
+ has_many :as_student_courses, through: :student_course_members, source: :course
+ has_many :manage_course_members, -> { teachers_and_admin }, class_name: 'CourseMember'
+ has_many :manage_courses, through: :manage_course_members, source: :course
+
+ # 关注
+ has_many :be_watchers, foreign_key: :user_id, dependent: :destroy # 我的关注
+ has_many :be_watcher_users, through: :be_watchers, dependent: :destroy # 我关注的用户
+
+ # 认证
+ has_many :apply_user_authentication
+ has_one :process_real_name_apply, -> { processing.real_name_auth.order(created_at: :desc) }, class_name: 'ApplyUserAuthentication'
+ has_one :process_professional_apply, -> { processing.professional_auth.order(created_at: :desc) }, class_name: 'ApplyUserAuthentication'
+ has_many :apply_actions, dependent: :destroy
+ has_many :trail_auth_apply_actions, -> { where(container_type: 'TrialAuthorization') }, class_name: 'ApplyAction'
+
+ has_many :attendances
+
+ # Groups and active users
+ scope :active, lambda { where(status: STATUS_ACTIVE) }
+
+ attr_accessor :password, :password_confirmation
+
+ before_save :update_hashed_password
+
+ #
+ # validations
+ #
+ validates_presence_of :login, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }, case_sensitive: false
+ validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, case_sensitive: false
+ validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, case_sensitive: false
+ validates_uniqueness_of :phone, :if => Proc.new { |user| user.phone_changed? && user.phone.present? }, case_sensitive: false
+ validates_length_of :login, maximum: LOGIN_LENGTH_LIMIT
+ validates_length_of :mail, maximum: MAIL_LENGTH_LMIT
+ # validates_format_of :mail, with: VALID_EMAIL_REGEX, multiline: true
+ # validates_format_of :phone, with: VALID_PHONE_REGEX, multiline: true
+ validate :validate_password_length
+
+ # validates :nickname, presence: true, length: { maximum: 10 }
+ # validates :lastname, presence: true
+
+ # 删除自动登录的token,一旦退出下次会提示需要登录
+ def delete_autologin_token(value)
+ Token.where(:user_id => id, :action => 'autologin', :value => value).delete_all
+ end
+
+ def delete_session_token(value)
+ Token.where(:user_id => id, :action => 'session', :value => value).delete_all
+ end
+
+
+ # 学号
+ def student_id
+ self.user_extension.try(:student_id)
+ end
+
+ # 关注总数
+ def following?(other_user)
+ relationships.find_by(followed_id: other_user)
+ end
+
+ # 关注
+ def follow!(other_user)
+ relationships.create!(followed_id: other_user)
+ end
+
+ # 取消关注
+ def unfollow!(other_user)
+ relationships.find_by(followed_id: other_user.id).destroy
+ end
+
+ # 判断当前用户是否为老师
+ def is_teacher?
+ self.user_extension.teacher?
+ end
+
+ # 平台认证的老师
+ def is_certification_teacher
+ self.user_extension.teacher? && self.professional_certification
+ end
+
+ def certification_teacher?
+ professional_certification? && user_extension.teacher?
+ end
+
+ # 判断用户的身份
+ def identity
+ ue = self.user_extension
+ unless ue.blank?
+ if ue.teacher?
+ ue.technical_title ? ue.technical_title : "老师"
+ elsif ue.student?
+ "学生"
+ else
+ ue.technical_title ? ue.technical_title : "专业人士"
+ end
+ end
+ end
+
+ # 判断当前用户是否通过职业认证
+ def pro_certification?
+ professional_certification
+ end
+
+ # 用户的学校名称
+ def school_name
+ user_extension&.school&.name || ''
+ end
+
+ def school_id
+ user_extension&.school_id
+ end
+
+ # 课堂的老师(创建者、老师、助教)
+ def teacher_of_course?(course)
+ course.course_members.exists?(user_id: id, role: [1,2,3], is_active: 1) || admin?
+ end
+
+ # 课堂的老师(创建者、老师、助教),不用考虑当前身份
+ def teacher_of_course_non_active?(course)
+ course.course_members.exists?(user_id: id, role: [1,2,3]) || admin?
+ end
+
+ # 是否是教师,课堂管理员或者超级管理员
+ def teacher_or_admin?(course)
+ course.course_members.exists?(user_id: id, role: [1,2], is_active: 1) || admin?
+ end
+
+ # 课堂的创建者(考虑到多重身份的用户)
+ def creator_of_course?(course)
+ course.course_members.exists?(user_id: id, role: 1, is_active: 1) || admin?
+ end
+
+ # 课堂的学生
+ def student_of_course?(course)
+ course.course_members.exists?(user_id: id, role: %i[STUDENT])
+ end
+
+ # 课堂成员
+ def member_of_course?(course)
+ course.course_members.exists?(user_id: id)
+ end
+
+ # 实训路径管理员:创建者或admin
+ def creator_of_subject?(subject)
+ subject.user_id == id || admin?
+ end
+
+ # 实训路径:合作者、admin
+ def manager_of_subject?(subject)
+ subject.subject_members.exists?(user_id: id, role: [1,2]) || admin?
+ end
+
+ # 实训管理员:实训合作者、admin
+ def manager_of_shixun?(shixun)
+ shixun.shixun_members.exists?(role: [1,2], user_id: id) || admin?
+ end
+
+ # 实训管理员
+ def creator_of_shixun?(shixun)
+ id == shixun.user_id
+ end
+
+ # 实训的合作者
+ def member_of_shixun?(shixun)
+ #self.shixun_members.where(:role => 2, :shixun_id => shixun.id).present?
+ shixun.shixun_members.exists?(role: 2, user_id: id)
+ end
+
+ # TPI的创建者
+ def creator_of_game?(game)
+ id == game.user_id
+ end
+
+ # 用户账号状态
+ def active?
+ status == STATUS_ACTIVE
+ end
+
+ def registered?
+ status == STATUS_REGISTERED
+ end
+
+ def locked?
+ status == STATUS_LOCKED
+ end
+
+ def activate
+ self.status = STATUS_ACTIVE
+ end
+
+ def register
+ self.status = STATUS_REGISTERED
+ end
+
+ def lock
+ self.status = STATUS_LOCKED
+ end
+
+ def activate!
+ update_attribute(:status, STATUS_ACTIVE)
+ end
+
+ def register!
+ update_attribute(:status, STATUS_REGISTERED)
+ end
+
+ def lock!
+ update_attribute(:status, STATUS_LOCKED)
+ end
+
+ # 课程用户身份
+ def course_identity(course)
+ if !logged?
+ Course::Anonymous
+ elsif admin?
+ Course::ADMIN
+ elsif business?
+ Course::BUSINESS
+ else
+ role = course.course_members.find_by(user_id: id, is_active: 1)&.role
+ case role
+ when nil then Course::NORMAL
+ when 'CREATOR' then Course::CREATOR
+ when 'PROFESSOR' then Course::PROFESSOR
+ when 'STUDENT' then Course::STUDENT
+ when 'ASSISTANT_PROFESSOR' then Course::ASSISTANT_PROFESSOR
+ end
+ end
+ end
+
+ # 实训用户身份
+ def shixun_identity(shixun)
+ @identity =
+ if admin?
+ User::EDU_ADMIN
+ elsif business?
+ User::EDU_BUSINESS
+ elsif creator_of_shixun?(shixun)
+ User::EDU_SHIXUN_MANAGER
+ elsif member_of_shixun?(shixun)
+ User::EDU_SHIXUN_MEMBER
+ elsif is_certification_teacher
+ User::EDU_CERTIFICATION_TEACHER
+ elsif is_teacher?
+ User::EDU_TEACHER
+ else
+ User::EDU_NORMAL
+ end
+ return @identity
+ end
+
+ # tpi的用户身份
+ def game_identity(game)
+ shixun = game.myshixun.shixun
+ @identity =
+ if admin?
+ User::EDU_ADMIN
+ elsif creator_of_shixun?(shixun)
+ User::EDU_SHIXUN_MANAGER
+ elsif member_of_shixun?(shixun)
+ User::EDU_SHIXUN_MEMBER
+ elsif is_certification_teacher
+ User::EDU_CERTIFICATION_TEACHER
+ elsif creator_of_game?(game)
+ User::EDU_GAME_MANAGER
+ elsif is_teacher?
+ User::EDU_TEACHER
+ else
+ User::EDU_NORMAL
+ end
+ return @identity
+ end
+
+ # 我的实训
+ def my_shixuns
+ shixun_ids = shixun_members.pluck(:shixun_id) + myshixuns.pluck(:shixun_id)
+ Shixun.where(:id => shixun_ids).visible
+ end
+
+ # 用户是否有权限查看实训
+ def shixun_permission(shixun)
+ # 性能优化:先处理不需要权限的实训(已发布并且没有单位权限限制的实训)
+ return true if manager_of_shixun?(shixun) # 实训管理员
+ return false if shixun.status != 2 || shixun.hidden # 隐藏或者未发布的实训:false
+ return true if shixun.use_scope == 0 # 对所有学校公开
+ return true if shixun.use_scope == 1 && shixun.shixun_schools.exists?(school_id: school_id) # 对部分高校公开
+ return false
+ end
+
+ # 用户在平台名称的显示方式
+ def full_name
+ return '游客' unless logged?
+
+ name = show_realname? ? lastname + firstname : nickname
+ name.blank? ? (nickname.blank? ? login : nickname) : name
+ end
+
+ # 用户的真实姓名(不考虑用户是否隐藏了真实姓名,课堂模块都用真实姓名)
+ def real_name
+ return '游客' unless logged?
+
+ name = lastname + firstname
+ name.blank? ? (nickname.blank? ? login : nickname) : name
+ name.gsub(/\s+/, '').strip #6.11 -hs
+ end
+
+ # 用户是否选题毕设课题
+ def selected_topic?(topic)
+ student_graduation_topics.where(graduation_topic_id: topic.id).last.try(:status)
+ end
+
+ def click_time
+ click_time = OnclickTime.find_by(user_id: id) || OnclickTime.create(user_id: id, onclick_time: created_on)
+ click_time.onclick_time
+ end
+
+ def manager_of_memo?(memo)
+ id == memo.author_id || admin?
+ end
+
+ # 是否是项目管理者
+ def manager_of_project?(project)
+ project.project_members.where(user_id: id).count > 0
+ end
+
+ def logged?
+ true
+ end
+
+ def active?
+ status == STATUS_ACTIVE
+ end
+
+ def locked?
+ status == STATUS_LOCKED
+ end
+
+ def phone_binded?
+ phone.present?
+ end
+
+ def self.current=(user)
+ Thread.current[:current_user] = user
+ end
+
+ def self.current
+ Thread.current[:current_user] ||= User.anonymous
+ end
+
+ def self.anonymous
+ anonymous_user = AnonymousUser.unscoped.take
+ if anonymous_user.nil?
+ anonymous_user = AnonymousUser.unscoped.create(lastname: 'Anonymous', firstname: '', login: '',
+ mail: '358551897@qq.com', phone: '13333333333', status: 0)
+ raise "Unable to create the anonymous user: error_info:#{anonymous_user.errors.messages}" if anonymous_user.new_record?
+ end
+ anonymous_user
+ end
+
+ # Returns the user who matches the given autologin +key+ or nil
+ def self.try_to_autologin(key)
+ user = Token.find_active_user('autologin', key)
+ user.update(last_login_on: Time.now) if user
+ user
+ end
+
+ def self.hash_password(clear_password)
+ Digest::SHA1.hexdigest(clear_password || "")
+ end
+
+ def check_password?(clear_password)
+ # Preventing Timing Attack
+ ActiveSupport::SecurityUtils.secure_compare(
+ User.hash_password("#{salt}#{User.hash_password clear_password}"),
+ hashed_password
+ )
+ end
+
+ # 登录,返回用户名与密码匹配的用户
+ def self.try_to_login(login, password)
+ login = login.to_s.strip
+ password = password.to_s
+
+ # Make sure no one can sign in with an empty login or password
+ return nil if login.empty? || password.empty?
+ if (login =~ VALID_EMAIL_REGEX)
+ user = find_by_mail(login)
+ elsif (login =~ VALID_PHONE_REGEX)
+ user = find_by_phone(login)
+ else
+ user = find_by_login(login)
+ end
+
+ if user
+ # user is already in local database
+ raise("账号已被注销,请联系管理员") if user.locked?
+ raise("密码错误") unless user.check_password?(password)
+ else
+ raise("账号未注册")
+ end
+
+ user
+ rescue => text
+ raise text
+ end
+
+ def show_real_name
+ name = lastname + firstname
+ if name.blank?
+ nickname.blank? ? login : nickname
+ else
+ name
+ end
+ end
+
+ def update_hashed_password
+ if password
+ salt_password(password)
+ end
+ end
+
+ def salt_password(clear_password)
+ self.salt = User.generate_salt
+ self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
+ end
+
+ def self.generate_salt
+ Educoder::Utils.random_hex(16)
+ end
+
+ protected
+ def validate_password_length
+ # 管理员的初始密码是5位
+ if password.present? && password.size < MIX_PASSWORD_LIMIT && !User.current.admin?
+ raise("密码长度不能低于#{MIX_PASSWORD_LIMIT}位")
+ end
+ end
+end
+
+
+class AnonymousUser < User
+ validate :validate_anonymous_uniqueness, :on => :create
+
+ def validate_anonymous_uniqueness
+ # There should be only one AnonymousUser in the database
+ errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
+ end
+
+ def available_custom_fields
+ []
+ end
+
+ # Overrides a few properties
+ def logged?; false end
+ def admin; false end
+ def name(*args); I18n.t(:label_user_anonymous) end
+ # def mail=(*args); nil end
+ # def mail; nil end
+ def time_zone; nil end
+ def rss_key; nil end
+
+
+ def membership(*args)
+ nil
+ end
+
+ def member_of?(*args)
+ false
+ end
+
+ # Anonymous user can not be destroyed
+ def destroy
+ false
+ end
+
+ protected
+
+ def instantiate_email_address
+ end
+
+end
diff --git a/app/models/user_action.rb b/app/models/user_action.rb
new file mode 100644
index 000000000..d3d70a66d
--- /dev/null
+++ b/app/models/user_action.rb
@@ -0,0 +1,2 @@
+class UserAction < ApplicationRecord
+end
diff --git a/app/models/user_day_certification.rb b/app/models/user_day_certification.rb
new file mode 100644
index 000000000..18da0fd12
--- /dev/null
+++ b/app/models/user_day_certification.rb
@@ -0,0 +1,2 @@
+class UserDayCertification < ApplicationRecord
+end
diff --git a/app/models/user_extension.rb b/app/models/user_extension.rb
new file mode 100644
index 000000000..3d8457ce3
--- /dev/null
+++ b/app/models/user_extension.rb
@@ -0,0 +1,12 @@
+class UserExtension < ApplicationRecord
+ # identity 0: 教师教授 1: 学生, 2: 专业人士, 3: 开发者
+ enum identity: { teacher: 0, student: 1, professional: 2, developer: 3 }
+
+ belongs_to :user
+ belongs_to :school
+ belongs_to :department
+
+ def identity_text
+ I18n.t("user.identity.#{identity}")
+ end
+end
diff --git a/app/models/verification_code.rb b/app/models/verification_code.rb
new file mode 100644
index 000000000..452e637a7
--- /dev/null
+++ b/app/models/verification_code.rb
@@ -0,0 +1,5 @@
+class VerificationCode < ApplicationRecord
+ def effective?
+ created_at + 10.minute > Time.current
+ end
+end
diff --git a/app/models/watcher.rb b/app/models/watcher.rb
new file mode 100644
index 000000000..d9426e03c
--- /dev/null
+++ b/app/models/watcher.rb
@@ -0,0 +1,4 @@
+class Watcher < ApplicationRecord
+ belongs_to :user
+ belongs_to :watchable, polymorphic: true
+end
\ No newline at end of file
diff --git a/app/models/zip_pack.rb b/app/models/zip_pack.rb
new file mode 100644
index 000000000..b6c0d08d4
--- /dev/null
+++ b/app/models/zip_pack.rb
@@ -0,0 +1,17 @@
+class ZipPack < ApplicationRecord
+
+ def self.packed?(bid, user_id, digests)
+ zip_pack = ZipPack.where(container_id: bid.id, container_type: bid.class.to_s, user_id: user_id).first
+ return false unless zip_pack && zip_pack.digests == digests
+ zip_pack
+ end
+
+ def file_valid?
+ return false unless File.exist?(self.file_path)
+ Educoder::Utils.digest(self.file_path) == self.file_digest
+ end
+
+ def digests
+ self.file_digests.split(',').sort
+ end
+end
diff --git a/app/services/application_service.rb b/app/services/application_service.rb
new file mode 100644
index 000000000..c6f66c098
--- /dev/null
+++ b/app/services/application_service.rb
@@ -0,0 +1,3 @@
+class ApplicationService
+ include Callable
+end
\ No newline at end of file
diff --git a/app/services/batch_export_shixun_report_service.rb b/app/services/batch_export_shixun_report_service.rb
new file mode 100644
index 000000000..6ea38b23f
--- /dev/null
+++ b/app/services/batch_export_shixun_report_service.rb
@@ -0,0 +1,55 @@
+class BatchExportShixunReportService
+ Error = Class.new(StandardError)
+
+ MAX_BATCH_LIMIT = 20
+
+ attr_reader :homework, :student_work_ids
+
+ def initialize(homework, student_work_ids)
+ @homework = homework
+ @student_work_ids = student_work_ids
+ end
+
+ def filename
+ @_filename ||= "#{Time.now.strftime('%Y%m%d%H%M%S')}-#{homework.name}.zip"
+ end
+
+ def zip
+ validate!
+ student_works = homework.student_works.where(id: student_work_ids).includes(:myshixun, user: :user_extension)
+
+ if student_works.count.zero?
+ raise Error, '请选择要导出的学生实训报告'
+ end
+ pdfs = []
+ zip_file = Tempfile.new(filename)
+ Zip::File.open(zip_file.path, Zip::File::CREATE) do |zip|
+ student_works.find_each.map do |student_work|
+ export = ExportShixunReportService.new(homework, student_work)
+ pdf = export.to_pdf
+ pdfs << pdf
+ begin
+ zip.add(export.filename, pdf.path)
+ rescue => ex
+ Rails.logger.error(ex.message)
+
+ zip.get_output_stream('FILE_NOTICE.txt'){|os| os.write('文件重复') }
+ next
+ end
+ end
+ zip_file
+ end
+ end
+
+ private
+
+ def validate!
+ if student_work_ids.size.zero?
+ raise Error, '请选择学生实训作业'
+ end
+
+ if student_work_ids.size > MAX_BATCH_LIMIT
+ raise Error, '导出实训报告太多,请分批导出'
+ end
+ end
+end
diff --git a/app/services/concerns/accepts_nested_attributes_helper.rb b/app/services/concerns/accepts_nested_attributes_helper.rb
new file mode 100644
index 000000000..7cee7cca1
--- /dev/null
+++ b/app/services/concerns/accepts_nested_attributes_helper.rb
@@ -0,0 +1,27 @@
+module AcceptsNestedAttributesHelper
+ extend ActiveSupport::Concern
+
+ def build_accepts_nested_attributes(obj, relations, data)
+ # 新记录,全部为创建
+ return data if obj.new_record?
+
+ # 更新时,需要处理删除数据
+ old_ids = relations.loaded? ? relations.map(&:id) : relations.pluck(:id)
+ new_ids =
+ data.map do |item|
+ yield(item) if block_given?
+
+ # 处理参数中错误的ID
+ item[:id] = item[:id].to_i
+ item[:id] = nil if item[:id].zero? || !old_ids.include?(item[:id])
+ item[:id]
+ end
+ new_ids.compact!
+
+ # 被删除的子项ID数组
+ destroy_ids = old_ids - new_ids
+ destroy_attributes = destroy_ids.map { |id| { id: id, _destroy: true } }
+
+ data.concat(destroy_attributes)
+ end
+end
\ No newline at end of file
diff --git a/app/services/concerns/callable.rb b/app/services/concerns/callable.rb
new file mode 100644
index 000000000..8c2fc75d1
--- /dev/null
+++ b/app/services/concerns/callable.rb
@@ -0,0 +1,9 @@
+module Callable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def call(*parameters)
+ new(*parameters).call
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/courses_service.rb b/app/services/courses_service.rb
new file mode 100644
index 000000000..8229c7c32
--- /dev/null
+++ b/app/services/courses_service.rb
@@ -0,0 +1,2 @@
+class CoursesService
+end
\ No newline at end of file
diff --git a/app/services/discusses_service.rb b/app/services/discusses_service.rb
new file mode 100644
index 000000000..91fa523c9
--- /dev/null
+++ b/app/services/discusses_service.rb
@@ -0,0 +1,200 @@
+# encoding=utf-8
+class DiscussesService
+ include ApplicationHelper
+ include GamesHelper
+
+ # “讨论区”的所有评论
+ def self.index params, current_user
+ page = params[:page].to_i
+ offset = page * 15
+ search = params[:search]
+ tag = params[:tag_repertoire_id]
+ sql, sql1, sq2 = '', '', ''
+ tidding_count = unviewed_tiddings(current_user) if current_user.present?
+ sql1 =
+ if search
+ "and d.content like '%#{search}%'"
+ end
+ sql2 =
+ if tag
+ shixun_ids = ShixunTagRepertoire.where(:tag_repertoire_id => tag).pluck(:shixun_id)
+ "and d.dis_id in(#{shixun_ids.join(",")})"
+ end
+ sql = "select d.id from discusses d join shixuns s on d.dis_id = s.id where s.status = 2 and s.hidden = false and d.root_id is null
+ and d.hidden = false #{sql1} #{sql2}"
+
+ memo_ids = Discuss.find_by_sql(sql).map(&:id)
+ Rails.logger.info("############memo_ids: #{memo_ids}")
+ memos = Discuss.field_for_list.where(:id => memo_ids).order("created_at desc")
+ memo_count = memos.count
+ memos = memos.offset(offset).limit(15)
+ # 实训标签使用最多的9个
+ hot_tags = TagRepertoire.find_by_sql("select distinct(a.name), a.id from
+ (select tr.id, tr.name, count(d.dis_id) cnt
+ from tag_repertoires tr join (shixun_tag_repertoires str
+ left join (shixuns s join discusses d on d.dis_id = s.id)
+ on s.id = str.shixun_id) on tr.id = str.tag_repertoire_id
+ group by d.dis_id order by cnt desc) a limit 9").map{|ht| ht.attributes.dup}
+ memos = memo_list memos
+ user_info = format_for_current_user current_user
+ hot_memos = Memo.field_for_recommend.posts.hot.limit(4)
+ hot_memos =
+ hot_memos.map do |hm|
+ replies_count = Memo.where(:root_id => hm.id).count
+ hm.attributes.dup.merge({replies_count: replies_count})
+ end
+
+ my_memos_count = Memo.user_posts(current_user.id).count
+ {memo_list: memos, memo_count: memo_count, hot_memos: hot_memos, hot_tags: hot_tags,
+ my_memos_count: my_memos_count, tidding_count: tidding_count, current_user: user_info,
+ recommend_shixuns: recommends}
+ end
+
+ # 添加评论
+ def create(params, current_user)
+ begin
+ Discuss.create!(
+ dis_id: params[:shixun_id], dis_type: 'Shixun', content: params[:content].gsub(' \;', '').strip, user_id: current_user.id,
+ praise_count: 0, position: params[:position], challenge_id: params[:challenge_id], hidden: !current_user.admin?
+ )
+ # 发送手机通知
+ Trustie::Sms.send(mobile:'18173242757', send_type:'discuss', name:'管理员')
+ rescue Exception => e
+ raise(e.message)
+ end
+ end
+
+ # 回复评论
+ def reply(params, current_user)
+ begin
+ base_dicuss params[:id]
+ Discuss.create!(
+ content: params[:content].gsub(' \;', '').strip, user_id: current_user.id, parent_id: params[:id],
+ root_id: @discuss.root_id || params[:id], praise_count: 0, challenge_id: @discuss.challenge_id,
+ dis_id: @discuss.dis_id, dis_type: @discuss.dis_type, position: @discuss.position, hidden: !current_user.admin?
+ )
+ rescue Exception => e
+ raise(e.message)
+ end
+ end
+
+ # 隐藏评论功能
+ def hidden(params, current_user)
+ discuss = Discuss.select([:id, :hidden, :dis_id]).find(params[:id])
+ shixun = Shixun.find(discuss.try(:dis_id))
+ if current_user.manager_of_shixun?(shixun)
+ if params[:hidden] == "1"
+ discuss.update_attribute(:hidden, true)
+ elsif params[:hidden] == "0"
+ discuss.update_column("hidden", false)
+ end
+ else
+ raise("你没有权限")
+ end
+ end
+
+ # 点/取消赞
+ # 一个用户只能一次
+ def plus params, current_user
+ pt = PraiseTread.where(:praise_tread_object_id => params[:id], :praise_tread_object_type => params[:container_type],
+ :user_id => current_user, :praise_or_tread => 1).first
+ # 如果当前用户已赞过,则不能重复赞
+ if params[:type] == 1 && pt.blank?
+ PraiseTread.create!(:praise_tread_object_id => params[:id], :praise_tread_object_type => params[:container_type],
+ :user_id => current_user.id, :praise_or_tread => 1) if pt.blank?
+ else
+ pt.destroy if pt.present? # 如果已赞过,则删掉这条赞(取消);如果没赞过,则为非法请求不处理
+ end
+
+ return {:praise_count => PraiseTread.where(:praise_tread_object_id => params[:id], :praise_tread_object_type => params[:container_type],
+ :praise_or_tread => 1).count}
+ end
+
+ # 奖励金币
+ def reward_code params, current_user
+ # 仅admin操作
+ unless current_user.admin?
+ raise("403")
+ end
+
+ ActiveRecord::Base.transaction do
+ container_id = params[:id]
+ container_type = params[:container_type]
+ score = params[:score]
+ user_id = params[:user_id]
+ user = User.select([:id, :login, :grade]).find(user_id)
+
+ # 金币来源记录
+ Grade.create(:user_id => user_id, :container_id => container_id, :container_type => container_type, :score => score)
+ # 更新用户总金币数
+ user.update_column(:grade, (score.to_i + user.grade.to_i))
+ # 多种类型都可以奖励金币
+ case container_type
+ when 'Memo', 'Post'
+ container = Memo.find(container_id)
+ score = score.to_i + container.reward.to_i
+ container.update_attribute(:reward, score)
+ when 'Discusses'
+ container = Discuss.select([:id, :reward]).find(params[:id])
+ score = score.to_i + container.reward.to_i
+ container.update_attribute(:reward, score)
+ end
+ return {:code => score}
+ end
+ end
+
+ # 基本查询
+ def base_dicuss id
+ @discuss = Discuss.select([:id, :hidden, :reward, :dis_type, :dis_id, :position, :challenge_id, :root_id]).find(id)
+ end
+
+ protected
+ def memo_list memos
+ memos.map do |m|
+ user = User.find(m.user_id)
+ praise_count = m.praise_tread.where(:praise_or_tread => 1).count
+ replies_count = Discuss.where(:root_id => m.id).count
+ shixun_tag = m.dis.tag_repertoires.map(&:name)
+ m.attributes.dup.except("user_id", "dis_id", "dis_type", "root_id").merge({
+ subject: (message_content m.content),
+ username: user.show_name,
+ login: user.login,
+ praise_count: praise_count,
+ replies_count: replies_count,
+ image_url: url_to_avatar(user),
+ shixun_tag: shixun_tag,
+ tpm_url: "/shixuns/#{m.dis.identifier}/shixun_discuss"
+ })
+ end
+ end
+
+ def format_for_current_user current_user
+ {username: current_user.show_name, login: current_user.login, user_id: current_user.id, image_url: url_to_avatar(current_user), admin: current_user.admin?}
+ end
+
+ def recommends
+ hot_shixuns = Shixun.field_for_recommend.published.order("myshixuns_count desc").limit(2)
+ newest_shixuns = Shixun.field_for_recommend.published.order("created_at desc").limit(2)
+ recommend_shixuns = []
+ hot_shixuns.each do |hs|
+ recommend_shixuns << {:id => hs.id, :name => hs.name, :identifier => hs.identifier, :myshixuns_count => hs.myshixuns_count, :image_url => url_to_avatar(hs)}
+ end
+ newest_shixuns.each do |ns|
+ recommend_shixuns << {:id => ns.id, :name => ns.name, :identifier => ns.identifier, :myshixuns_count => ns.myshixuns_count, :image_url => url_to_avatar(ns)}
+ end
+ return recommend_shixuns
+ end
+
+ # 将数据库对象转换成哈希对象
+ def object_to_hash objects
+ objects.map{|o| o.attributes.dup}
+ end
+
+ def unviewed_tiddings current_user
+ new_tidings_count = current_user.tidings.where("created_at > '#{current_user.onclick_time.onclick_time}'").count
+ new_pri_message_count = current_user.private_messages.where("created_at > '#{current_user.onclick_time.onclick_time}'").count
+ count = new_tidings_count + new_pri_message_count
+ return count
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/duplicate_course_service.rb b/app/services/duplicate_course_service.rb
new file mode 100644
index 000000000..c9e991d7f
--- /dev/null
+++ b/app/services/duplicate_course_service.rb
@@ -0,0 +1,147 @@
+class DuplicateCourseService < ApplicationService
+ attr_reader :origin_course, :user, :course
+
+ def initialize(origin_course, user)
+ @user = user
+ @origin_course = origin_course
+ end
+
+ def call
+ ActiveRecord::Base.transaction do
+ @course = copy_course!
+
+ copy_course_modules!
+
+ join_course!
+
+ copy_homework_commons!
+
+ copy_exercises!
+
+ copy_polls!
+
+ copy_attachments!
+
+ course
+ end
+ end
+
+ private
+
+ def copy_course!
+ create_attrs = origin_course.as_json(only: %i[name class_period credit course_list_id])
+ create_attrs.merge!(tea_id: user.id, school_id: user.school_id, is_public: 0, is_copy: 1)
+
+ Course.create!(create_attrs)
+ end
+
+ def copy_course_modules!
+ origin_course.course_modules.each do |course_module|
+ attrs = course_module.as_json(only: %i[module_type position hidden module_name])
+ CourseModule.create!(attrs.merge(course_id: course.id))
+ end
+ end
+
+ def join_course!
+ CourseMember.create!(course_id: course.id, user_id: user.id, role: 1)
+ end
+
+ def copy_homework_commons!
+ origin_course.homework_commons.where(homework_type: %i[normal group practice]).find_each do |origin_homework|
+ homework_attrs = origin_homework.as_json(only: %i[name description homework_type homework_bank_id reference_answer])
+
+ homework = HomeworkCommon.create!(homework_attrs.merge(user_id: user.id, course_id: course.id))
+
+ origin_homework.attachments.find_each do |origin_attachment|
+ attachment = origin_attachment.copy
+ attrs = { container: homework, author_id: origin_homework.user_id, copy_from: origin_attachment.id }
+ attachment.assign_attributes(attrs)
+ attachment.save!
+
+ origin_attachment.increment!(:quotes)
+ end
+
+ homework.create_homework_detail_manual!
+
+ if homework.group_homework_type?
+ attrs = origin_homework.homework_detail_group.as_json(only: %i[min_num max_num base_on_project])
+ homework.create_homework_detail_group!(attrs)
+ elsif homework.practice_homework_type?
+ HomeworkCommonsShixun.create!(homework_common_id: homework.id, shixun_id: origin_homework.homework_commons_shixun.shixun_id)
+ HomeworksService.new.create_shixun_homework_cha_setting(homework, origin_homework.shixuns.first)
+ end
+
+ origin_homework.increment!(:quotes)
+ origin_homework.homework_bank.increment!(:quotes) if origin_homework.homework_bank
+ end
+ end
+
+ def copy_exercises!
+ origin_course.exercises.find_each do |origin_exercise|
+ attrs = origin_exercise.as_json(only: %i[exercise_name exercise_description exercise_bank_id])
+ exercise = course.exercises.create!(attrs.merge(user_id: user.id))
+
+ origin_exercise.exercise_questions.find_each do |origin_question|
+ question_attrs = origin_question.as_json(only: %i[question_title question_type question_number question_score])
+ question_attrs[:question_type] ||= 1
+ question = exercise.exercise_questions.create!(question_attrs)
+
+ exercise_choice_map = {}
+ origin_question.exercise_choices.each_with_index do |origin_choice, index|
+ choice_attrs = { choice_position: index + 1, choice_text: origin_choice.choice_text }
+ choice = question.exercise_choices.create!(choice_attrs)
+
+ exercise_choice_map[origin_choice.id] = choice.id
+ end
+
+ origin_question.exercise_standard_answers.find_each do |origin_answer|
+ question.exercise_standard_answers.create!(
+ exercise_choice_id: exercise_choice_map[origin_answer.exercise_choice_id],
+ answer_text: origin_answer.answer_text
+ )
+ end
+ end
+
+ origin_exercise.exercise_bank.increment!(:quotes) if exercise.exercise_bank
+ end
+ end
+
+ def copy_polls!
+ origin_course.polls.includes(poll_questions: :poll_answers).find_each do |origin_poll|
+ poll_attrs = origin_poll.as_json(only: %i[polls_name polls_description exercise_bank_id])
+ poll = course.polls.create!(poll_attrs.merge(user_id: user.id))
+
+ origin_poll.poll_questions.each do |origin_question|
+ attr_names = %i[question_title question_type is_necessary question_number max_choices min_choices]
+ question_attrs = origin_question.as_json(only: attr_names)
+ question_attrs[:question_type] ||= 1
+
+ question = poll.poll_questions.create!(question_attrs)
+
+ origin_question.poll_answers.each_with_index do |origin_answer, index|
+ question.poll_answers.create!(answer_position: index + 1, answer_text: origin_answer.answer_text)
+ end
+ end
+
+ origin_poll.exercise_bank.increment!(:quotes) if origin_poll.exercise_bank
+ end
+ end
+
+ def copy_attachments!
+ origin_course.attachments.each do |origin_attachment|
+ attachment = origin_attachment.copy
+ attachment.tag_list.add(origin_attachment.tag_list) # tag关联
+ attachment.container = course
+ attachment.created_on = Time.now
+ attachment.publish_time = nil
+ attachment.author_id = User.current.id
+ attachment.copy_from = origin_attachment.copy_from || origin_attachment.id
+ attachment.is_publish = 0
+ attachment.attachtype ||= 4
+
+ attachment.save!
+
+ origin_course.update_quotes(attachment)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/calculate_course_evaluation_service.rb b/app/services/ecs/calculate_course_evaluation_service.rb
new file mode 100644
index 000000000..bd551dbf4
--- /dev/null
+++ b/app/services/ecs/calculate_course_evaluation_service.rb
@@ -0,0 +1,124 @@
+class Ecs::CalculateCourseEvaluationService < ApplicationService
+ attr_reader :ec_course
+
+ def initialize(ec_course)
+ @ec_course = ec_course
+ end
+
+ def call
+ ActiveRecord::Base.transaction do
+ clear_student_score!
+
+ calculate_student_score_target!
+
+ calculate_graduation_requirement!
+ end
+ end
+
+ private
+
+ def clear_student_score!
+ ec_course.ec_course_student_scores.destroy_all
+ end
+
+ def requirement_passed?(standard_value, real_value)
+ standard_value && real_value && real_value >= standard_value ? 1 : 0
+ end
+
+ def calculate_student_score_target!
+ complete_target_count = 0
+ course_targets = ec_course.ec_course_targets.includes(ec_achievement_evaluation_relates: :ec_course_achievement_method)
+ course_targets.find_each do |target|
+ target.ec_achievement_evaluation_relates.each do |relate|
+ achievement_method = relate.ec_course_achievement_method
+
+ total = achievement_method.ec_achievement_evaluation_relates.count
+ percentage = total.zero? ? 0 : achievement_method.percentage.fdiv(total)
+
+ achievements = EcStudentAchievement.where(ec_course_evaluation_id: achievement_method.ec_course_evaluation_id)
+
+ achievements =
+ if relate.ec_course_evaluation_subitem_id.blank? || relate.ec_course_evaluation_subitem_id == -1
+ achievements.where(position: relate.position)
+ else
+ achievements.where(ec_course_evaluation_subitem_id: relate.ec_course_evaluation_subitem_id)
+ end
+
+ # 遍历学生成绩统计数据
+ achievements.find_each do |achievement|
+ student_score = EcCourseStudentScore.find_or_initialize_by(
+ ec_course_id: ec_course.id,
+ ec_year_student_id: achievement.ec_year_student_id
+ )
+
+ score = achievement_method.score.zero? || achievement.score.nil? ? 0 : (achievement.fdiv(achievement_method.score) * percentage).round(3)
+
+ if student_score.new_record?
+ student_score.student_name = achievement.student_name
+ student_score.student_number = achievement.student_number
+ student_score.save!
+
+ create_params = {
+ ec_course_id: ec_course.id, ec_year_student_id: achievement.ec_year_student_id,
+ ec_course_target_id: target.id, eaer_id: relate.id,
+ ec_target_position: target.position, score: score
+ }
+ student_score.ec_student_score_targets.create!(create_params)
+ else
+ score_target = EcStudentScoreTarget.find_or_initialize_by(
+ ec_course_id: ec_course.id,
+ ec_year_student_id: achievement.ec_year_student_id,
+ ec_course_student_score_id: student_score.id,
+ ec_course_target_id: target.id,
+ ec_target_position: target.position,
+ eaer_id: relate.id
+ )
+
+ score_target.score = score_target.score.to_f + score
+ score_target.save!
+ end
+ end
+ end
+
+ # 计算该课程目标是否完成
+ count = target.ec_student_score_targets.count
+ score = target.ec_student_score_targets.sum(:score)
+ average = count.zero? ? 0 : score.fdiv(count)
+ complete_target_count += 1 if target.standard_grade && target.standard_grade <= average
+ end
+
+ ec_course.update!(complete_target_count: complete_target_count)
+ end
+
+ def calculate_graduation_requirement!
+ student_scores = ec_course.ec_course_student_scores.joins(:ec_course_target).group(:ec_course_target_id)
+ student_scores = student_scores.select('AVG(score) as average_score, ec_course_target_id')
+ student_score_map = student_scores.group_by { |item| item.ec_course_target_id }
+
+ subitem_targets = ec_course.ec_graduation_subitem_course_targets.includes(:ec_graduation_subitem, :ec_course_target)
+ subitem_targets.group_by { |subitem_target| subitem_target.ec_graduation_subitem }.each do |subitem, subitem_target_arr|
+ support = subitem.ec_course_supports.find_by(ec_course_id: ec_course.id)
+ next if support.blank?
+
+ total_weight = 0.0
+ total_score = 0.0
+ subitem_target_arr.each do |subitem_target|
+ target = subitem_target.ec_course_target
+ student_score = student_score_map[target.id].first
+
+ total_weight += target.weigths.to_f
+ total_score += student_score.average_score.round(2) * target.weigths.to_f
+ end
+
+ target_value = support.weights.to_f * ec_course.ec_year.calculation_value.to_f
+ real_value = total_weight.zero? ? 0 : (total_score * support.weights.to_f) / (total_weight * 100)
+
+ cal = support.ec_graduation_requirement_calculation || support.build_ec_graduation_requirement_calculation
+
+ cal.target_value = target_value.round(3)
+ cal.real_value = real_value.round(3)
+ cal.status = requirement_passed?(target_value, real_value)
+ cal.save!
+ end
+ end
+end
diff --git a/app/services/ecs/copy_ec_year_service.rb b/app/services/ecs/copy_ec_year_service.rb
new file mode 100644
index 000000000..87cbe0845
--- /dev/null
+++ b/app/services/ecs/copy_ec_year_service.rb
@@ -0,0 +1,344 @@
+class CopyEcYearService < ApplicationService
+ attr_reader :major_school, :to_year
+
+ def initialize(major_school, year)
+ @major_school = major_school
+ @to_year = major_school.ec_years.new(year: year)
+ end
+
+ def call
+ if from_year.blank?
+ to_year.save!
+ return to_year
+ end
+
+ # 专业第一次创建届别时,复制示例专业2017届
+ ActiveRecord::Base.transaction do
+ copy_ec_year!
+
+ copy_graduation_requirement!
+
+ copy_training_objective!
+
+ new_major_school? ? copy_template_ec_course! : copy_ec_courses!
+ end
+
+ to_year
+ end
+
+ private
+
+ def new_major_school?
+ @_new_major ||= major_school.ec_years.count.zero?
+ end
+
+ def from_year
+ @_from_year ||= new_major_school? ? template_major_year : to_year.prev_year
+ end
+
+ def template_major_year
+ EcYear.joins(:ec_major_school).where(ec_major_schools: { template_major: true }).find_by_year('2017')
+ end
+
+ def copy_ec_year!
+ to_year.calculation_value = from_year.calculation_value
+ to_year.save!
+ end
+
+ def copy_graduation_requirement!
+ requirements = from_year.ec_graduation_requirements.includes(ec_graduation_subitems: :ec_require_sub_vs_standards)
+
+ requirements.each do |requirement|
+ to_requirement = to_year.ec_graduation_requirements.new
+ to_requirement.attributes = requirement.attributes.except('id', 'ec_year_id', 'created_at', 'updated_at')
+ to_requirement.save!
+
+ # 记录对应关系,创建支撑时使用
+ graduation_requirement_map[requirement.id] = to_requirement.id
+
+ copy_graduation_subitems(requirement, to_requirement)
+ end
+ end
+
+ def copy_graduation_subitems(requirement, to_requirement)
+ requirement.ec_graduation_subitems.each do |item|
+ to_item = to_requirement.ec_graduation_subitems.new
+ to_item.attributes = item.attributes.except('id', 'ec_graduation_requirement_id', 'created_at', 'updated_at')
+ to_item.save!
+
+ # 记录对应关系,创建支撑时使用
+ graduation_subitem_map[item.id] = to_item.id
+
+ copy_requirement_standard_supports!(item, to_item)
+ end
+ end
+
+ def copy_requirement_standard_supports!(graduation_subitem, to_graduation_subitem)
+ graduation_subitem.ec_require_sub_vs_standards.each do |support|
+ to_support = to_graduation_subitem.ec_require_sub_vs_standards.new
+ to_support.attributes = support.attributes.except('id', 'ec_graduation_subitem_id', 'created_at', 'updated_at')
+ to_support.save!
+ end
+ end
+
+ def copy_training_objective!
+ training_objective = from_year.ec_training_objective
+ return if training_objective.blank?
+
+ attributes = training_objective.attributes.except('id', 'ec_year_id', 'created_at', 'updated_at')
+ to_training_objective = to_year.create_ec_training_objective!(attributes)
+
+ copy_training_subitems!(training_objective, to_training_objective)
+ end
+
+ def copy_training_subitems!(training_objective, to_training_objective)
+ training_subitems = training_objective.ec_training_subitems.includes(:ec_requirement_vs_objectives)
+
+ training_subitems.each do |item|
+ to_item = to_training_objective.ec_training_subitems.new
+ to_item.attributes = item.attributes.except('id', 'ec_training_objective_id', 'created_at', 'updated_at')
+ to_item.save!
+
+ copy_requirement_vs_objectives!(item, to_item)
+ end
+ end
+
+ def copy_requirement_vs_objectives!(training_item, to_training_item)
+ training_item.ec_requirement_vs_objectives.each do |support|
+ to_support = to_training_item.ec_requirement_vs_objectives.new(status: support.status)
+ to_support.ec_graduation_requirement_id = graduation_requirement_map[support.ec_graduation_requirement_id]
+ to_support.save!
+ end
+ end
+
+ def copy_template_ec_course!
+ course = from_year.ec_courses.includes(
+ :ec_score_levels,
+ ec_course_evaluations: :ec_course_evaluation_subitems,
+ ec_course_targets: :ec_graduation_subitem_course_targets,
+ ec_course_achievement_methods: :ec_achievement_evaluation_relates,
+ ec_course_supports: :ec_graduation_subitem_courses
+ ).find_by_name('数据库原理')
+
+ to_course = to_year.ec_courses.new
+ to_course.attributes = course.attributes.except('id', 'ec_year_id', 'created_at', 'updated_at')
+ to_course.save!
+
+ course_map[course.id] = to_course.id
+
+ copy_course_evaluations!(course, to_course)
+ copy_course_targets!(course, to_course)
+ copy_course_achievement_methods!(course, to_course)
+ copy_ec_course_supports!(course, to_course)
+ copy_score_levels!(course, to_course)
+
+ # 复制示例时需要复制学生和成绩数据
+ copy_year_students!
+ end
+
+ def copy_ec_courses!
+ courses = from_year.ec_courses.includes(
+ :ec_score_levels,
+ ec_course_evaluations: :ec_course_evaluation_subitems,
+ ec_course_targets: :ec_graduation_subitem_course_targets,
+ ec_course_achievement_methods: :ec_achievement_evaluation_relates,
+ ec_course_supports: :ec_graduation_subitem_courses
+ )
+
+ courses.each do |course|
+ to_course = to_year.ec_courses.new
+ to_course.attributes = course.attributes.except('id', 'ec_year_id', 'created_at', 'updated_at')
+ to_course.save!
+
+ course_map[course.id] = to_course.id
+
+ copy_course_evaluations!(course, to_course)
+ copy_course_targets!(course, to_course)
+ copy_course_achievement_methods!(course, to_course)
+ copy_ec_course_supports!(course, to_course)
+ copy_score_levels!(course, to_course)
+ end
+ end
+
+ def copy_course_evaluations!(course, to_course)
+ course.ec_course_evaluations.each do |evaluation|
+ to_evaluation = to_course.ec_course_evaluations.new
+ to_evaluation.attributes = evaluation.attributes.except('id', 'ec_course_id', 'created_at', 'updated_at')
+ to_evaluation.save!
+
+ course_evaluation_map[evaluation.id] = to_evaluation.id
+
+ copy_course_evaluation_subitems!(evaluation, to_evaluation)
+ end
+ end
+
+ def copy_course_evaluation_subitems!(evaluation, to_evaluation)
+ evaluation.ec_course_evaluation_subitems.each do |item|
+ to_item = to_evaluation.ec_course_evaluation_subitems.new
+ to_item.attributes = item.attributes.except('id', 'ec_course_evaluation_id', 'created_at', 'updated_at')
+ to_item.save!
+
+ course_evaluation_subitem_map[item.id] = to_item.id
+ end
+ end
+
+ def copy_course_targets!(course, to_course)
+ course.ec_course_targets.each do |target|
+ to_target = to_course.ec_course_targets.new
+ to_target.attributes = target.attributes.except('id', 'ec_course_id', 'created_at', 'updated_at')
+ to_target.save!
+
+ course_target_map[target.id] = to_target.id
+
+ copy_graduation_subitem_course_targets!(target, to_target)
+ end
+ end
+
+ def copy_graduation_subitem_course_targets!(target, to_target)
+ target.ec_graduation_subitem_course_targets.each do |support|
+ to_support = to_target.ec_graduation_subitem_course_targets.new
+ to_support.attributes = support.attributes.except('id', 'ec_course_target_id', 'ec_graduation_subitem_id', 'created_at', 'updated_at')
+ to_support.ec_graduation_subitem_id = graduation_subitem_map[support.ec_graduation_subitem_id]
+ to_support.save!
+ end
+ end
+
+ def copy_course_achievement_methods!(course, to_course)
+ course.ec_course_achievement_methods.each do |from|
+ to = to_course.ec_course_achievement_methods.new
+ to.attributes = from.attributes.except('id', 'ec_course_id', 'ec_course_target_id', 'ec_course_evaluation_id',
+ 'ec_course_evaluation_subitem_id', 'created_at', 'updated_at')
+
+ to.ec_course_target_id = course_target_map[from.ec_course_target_id]
+ to.ec_course_evaluation_id = course_evaluation_map[from.ec_course_evaluation_id]
+ to.ec_course_evaluation_subitem_id = course_evaluation_subitem_map[from.ec_course_evaluation_subitem_id]
+ to.save!
+
+ copy_achievement_evaluation_relates!(from, to)
+ end
+ end
+
+ def copy_achievement_evaluation_relates!(method, to_method)
+ method.ec_achievement_evaluation_relates.each do |relate|
+ to_relate = to_method.ec_achievement_evaluation_relates.new
+ to_relate.attributes = relate.attributes.except('id', 'ec_course_achievement_method_id', 'ec_course_target_id',
+ 'ec_course_evaluation_subitem_id', 'created_at', 'updated_at')
+ to_relate.ec_course_target_id = course_target_map[relate.ec_course_target_id]
+ # 可能不存在,所以为 -1
+ to_relate.ec_course_evaluation_subitem_id = course_evaluation_subitem_map[relate.ec_course_evaluation_subitem_id] || -1
+ to_relate.save!
+
+ achievement_evaluation_relates_map[relate.id] = to_relate.id
+ end
+ end
+
+ def copy_ec_course_supports!(course, to_course)
+ course.ec_course_supports.each do |support|
+ to_support = to_course.ec_course_supports.new
+ to_support.attributes = support.attributes.except('id', 'ec_course_id', 'created_at', 'updated_at')
+ to_support.save!
+
+ copy_graduation_subitem_courses!(support, to_support)
+ end
+ end
+
+ def copy_graduation_subitem_courses!(course_support, to_course_support)
+ course_support.ec_graduation_subitem_courses.each do |item|
+ to_item = to_course_support.ec_graduation_subitem_courses.new
+ to_item.attributes = item.attributes.except('id', 'ec_course_support_id', 'ec_graduation_subitem_id',
+ 'created_at', 'updated_at')
+ to_item.ec_graduation_subitem_id = graduation_subitem_map[item.ec_graduation_subitem_id]
+ to_item.save!
+ end
+ end
+
+ def copy_score_levels!(course, to_course)
+ course.ec_score_levels.each do |level|
+ to_level = to_course.ec_score_levels.new
+ to_level.attributes = level.attributes.except('id', 'ec_course_id', 'created_at', 'updated_at')
+ to_level.save!
+ end
+ end
+
+ def copy_year_students!
+ students = from_year.ec_year_students.includes(:ec_student_achievements, :ec_course_student_scores, :ec_student_score_targets)
+
+ students.each do |student|
+ to_student = to_year.ec_year_students.new
+ to_student.attributes = student.attributes.except('id', 'ec_year_id', 'created_at', 'updated_at')
+ to_student.save!
+
+ copy_student_achievements!(student, to_student)
+ copy_course_student_scores!(student, to_student)
+ copy_student_score_targets!(student, to_student)
+ end
+ end
+
+ def copy_student_achievements!(student, to_student)
+ student.ec_student_achievements.each do |achievement|
+ to_achievement = to_student.ec_student_achievements.new
+ to_achievement.attributes = achievement.attributes.except('id', 'ec_year_student_id', 'ec_course_evaluation_id',
+ 'ec_course_evaluation_subitem_id', 'created_at', 'updated_at')
+ to_achievement.ec_course_evaluation_id = course_evaluation_map[achievement.ec_course_evaluation_id]
+ to_achievement.ec_course_evaluation_subitem_id = course_evaluation_subitem_map[achievement.ec_course_evaluation_subitem_id]
+ to_achievement.save!
+ end
+ end
+
+ def copy_course_student_scores!(student, to_student)
+ student.ec_course_student_scores.each do |score|
+ to_score = to_student.ec_course_student_scores.new
+ to_score.attributes = score.attributes.except('id', 'ec_year_student_id', 'ec_course_id', 'created_at', 'updated_at')
+ to_score.ec_course_id = course_map[score.ec_course_id]
+ to_score.save!
+
+ course_student_score_map[score.id] = to_score.id
+ end
+ end
+
+ def copy_student_score_targets!(student, to_student)
+ student.ec_student_score_targets.each do |target|
+ to_target = to_student.ec_student_score_targets.new
+ to_target.attributes = target.attributes.except('id', 'ec_course_id', 'ec_course_student_score_id',
+ 'ec_course_target_id', 'ec_year_student_id', 'eaer_id',
+ 'created_at', 'updated_at')
+ to_target.ec_course_id = course_map[target.ec_course_id]
+ to_target.ec_course_student_score_id = course_student_score_map[target.ec_course_student_score_id]
+ to_target.ec_course_target_id = course_target_map[target.ec_course_target_id]
+ to_target.eaer_id = achievement_evaluation_relates_map[target.eaer_id]
+ to_target.save!
+ end
+ end
+
+ def graduation_requirement_map
+ @_graduation_requirement_map ||= {}
+ end
+
+ def graduation_subitem_map
+ @_graduation_subitem_map ||= {}
+ end
+
+ def course_map
+ @_course_map ||= {}
+ end
+
+ def course_evaluation_map
+ @_course_evaluation_map ||= {}
+ end
+
+ def course_evaluation_subitem_map
+ @_course_evaluation_subitem_map ||= {}
+ end
+
+ def achievement_evaluation_relates_map
+ @_achievement_evaluation_relates_map ||= {}
+ end
+
+ def course_target_map
+ @_course_target_map ||= {}
+ end
+
+ def course_student_score_map
+ @_course_student_score_map ||= {}
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/create_course_achievement_method_service.rb b/app/services/ecs/create_course_achievement_method_service.rb
new file mode 100644
index 000000000..01f09ff2b
--- /dev/null
+++ b/app/services/ecs/create_course_achievement_method_service.rb
@@ -0,0 +1,66 @@
+class Ecs::CreateCourseAchievementMethodsService < ApplicationService
+ include AcceptsNestedAttributesHelper
+
+ attr_reader :course_target, :params
+
+ def initialize(course_target, params)
+ @course_target = course_target
+ @params = params
+ end
+
+ def call
+ Ecs::CreateCourseAchievementMethodsForm.new(
+ ec_course: course_target.ec_course,
+ course_achievement_methods: params[:course_achievement_methods]
+ ).validate!
+
+ accepts_attributes = build_accepts_nested_attributes(
+ course_target, course_target.ec_course_achievement_methods,
+ params[:course_achievement_methods],
+ &method(:deal_course_achievement_method_attribute!)
+ )
+
+ course_target.assign_attributes(ec_course_achievement_methods_attributes: accepts_attributes)
+ course_target.save!
+ course_target
+ end
+
+ private
+
+ def deal_course_achievement_method_attribute!(item)
+ # 创建新的评价方法时,全部为新建
+ if item[:id].blank? || course_target.ec_course_achievement_methods.find_by(id: item[:id]).blank?
+ item[:ec_achievement_evaluation_relates_attributes] =
+ Array(*item[:course_evaluation_subitem_ids]).uniq.map do |subitem_id|
+ { ec_course_evaluation_subitem_id: subitem_id.to_i }
+ end
+ return
+ end
+
+ achievement_method = course_target.ec_course_achievement_methods.find_by(id: item[:id])
+ relates = achievement_method.ec_achievement_evaluation_relates
+
+ # 获取传入的 subitem id数组和已存在的 subitem id数组
+ old_subitem_ids = relates.map(&:ec_course_evaluation_subitem_id).uniq
+ new_subitem_ids = Array(*item[:course_evaluation_subitem_ids]).map(&:to_i).uniq
+
+ # 分别得到需要移除的 subitem ID数组和需要创建的 subitem ID数组
+ destroy_subitem_ids = old_subitem_ids - new_subitem_ids
+ create_subitem_ids = new_subitem_ids - old_subitem_ids
+
+ # 生成需要创建关系的 subitem id 数据
+ create_attributes = create_subitem_ids.map { |item_id| { ec_course_evaluation_subitem_id: item_id } }
+
+ # 处理需要更新或者删除的记录
+ exists_attributes = relates.map do |relate|
+ if destroy_subitem_ids.include?(relate.ec_course_evaluation_subitem_id)
+ { id: relate.id, _destroy: true }
+ else
+ relate.as_json(only: %i[id ec_course_evaluation_subitem_id ec_course_achievement_method_id])
+ end
+ end
+
+ # 相加得到accepts_nested_attributes
+ item[:ec_achievement_evaluation_relates_attributes] = create_attributes + exists_attributes
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/create_course_manager_service.rb b/app/services/ecs/create_course_manager_service.rb
new file mode 100644
index 000000000..58e803bcf
--- /dev/null
+++ b/app/services/ecs/create_course_manager_service.rb
@@ -0,0 +1,29 @@
+class Ecs::CreateCourseManagerService < ApplicationService
+ Error = Class.new(StandardError)
+
+ COURSE_MANAGER_COUNT_LIMIT = 2 # 课程管理员数量限制
+
+ attr_reader :ec_course, :user_id
+
+ def initialize(ec_course, user_id)
+ @ec_course = ec_course
+ @user_id = user_id
+ end
+
+ def call
+ user = User.find_by(id: params[:user_id])
+ raise Error, '该用户不存在' if user.blank?
+
+ if ec_course.ec_course_users.exists?(user_id: user.id)
+ raise Error, '该用户已经是该课程的管理员了'
+ end
+
+ if ec_course.ec_course_users.count >= COURSE_MANAGER_COUNT_LIMIT
+ raise Error, '该课程管理员数量已达上限'
+ end
+
+ ec_course.ec_course_users.create!(user: user)
+
+ user
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/create_course_service.rb b/app/services/ecs/create_course_service.rb
new file mode 100644
index 000000000..dba162189
--- /dev/null
+++ b/app/services/ecs/create_course_service.rb
@@ -0,0 +1,42 @@
+class Ecs::CreateCourseService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :ec_year, :params
+
+ def initialize(ec_year, params)
+ @ec_year = ec_year
+ @params = params
+ end
+
+ def call
+ name = params[:name].to_s.strip
+ if ec_year.ec_courses.exists?(name: name)
+ raise Error, '课程名称重复'
+ end
+
+ ec_course = nil
+ ActiveRecord::Base.transaction do
+ ec_course = ec_year.ec_courses.create!(name: name)
+
+ create_default_score_levels!(ec_course)
+ end
+
+ ec_course
+ end
+
+ private
+
+ def create_default_score_levels!(ec_course)
+ EcScoreLevel.bulk_insert(:ec_course_id, :score, :level, :position) do |worker|
+ [
+ { ec_course_id: ec_course.id, score: 90, level: '优秀', position: 1 },
+ { ec_course_id: ec_course.id, score: 80, level: '良好', position: 2 },
+ { ec_course_id: ec_course.id, score: 70, level: '中等', position: 3 },
+ { ec_course_id: ec_course.id, score: 60, level: '及格', position: 4 },
+ { ec_course_id: ec_course.id, score: 60, level: '不及格', position: 5 },
+ ].each do |attributes|
+ worker.add attributes
+ end
+ end
+ end
+end
diff --git a/app/services/ecs/create_course_targets_service.rb b/app/services/ecs/create_course_targets_service.rb
new file mode 100644
index 000000000..8ef3afea7
--- /dev/null
+++ b/app/services/ecs/create_course_targets_service.rb
@@ -0,0 +1,31 @@
+class Ecs::CreateCourseTargetsService < ApplicationService
+ include AcceptsNestedAttributesHelper
+
+ attr_reader :ec_course, :params
+
+ def initialize(ec_course, params)
+ @ec_course = ec_course
+ @params = params
+ end
+
+ def call
+ accepts_attributes = build_accepts_nested_attributes(
+ ec_course, ec_course.ec_course_targets,
+ params[:course_targets],
+ &method(:deal_course_target_attribute!) # 相当于方法后面写代码块,然后把参数传入方法: a(&method(:b)) == a { |c| b(c) }
+ )
+
+ ec_course.assign_attributes(ec_course_targets_attributes: accepts_attributes)
+ ec_course.save!
+ ec_course
+ end
+
+ private
+
+ def deal_course_target_attribute!(target)
+ target[:content] = target[:content].to_s.strip
+ target[:weight] = target[:weight].to_f.round(2)
+ target[:ec_graduation_subitem_course_targets_attributes] =
+ [{ ec_graduation_subitem_id: target.delete(:graduation_subitem_id) }]
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/create_major_manager_service.rb b/app/services/ecs/create_major_manager_service.rb
new file mode 100644
index 000000000..befe80706
--- /dev/null
+++ b/app/services/ecs/create_major_manager_service.rb
@@ -0,0 +1,31 @@
+class Ecs::CreateMajorManagerService < ApplicationService
+ Error = Class.new(StandardError)
+
+ MAJOR_MANAGER_COUNT_LIMIT = 5 # 专业管理员数量限制
+
+ attr_reader :major_school, :user_id
+
+ def initialize(major_school, user_id)
+ @major_school = major_school
+ @user_id = user_id
+ end
+
+ def call
+ raise Error, '示例专业不能添加管理员' if major_school.template_major?
+
+ user = User.find_by(id: params[:user_id])
+ raise Error, '该用户不存在' if user.blank?
+
+ if major_school.ec_major_school_users.exists?(user_id: user.id)
+ raise Error, '该用户已经是该专业的管理员了'
+ end
+
+ if major_school.ec_major_school_users.count >= MAJOR_MANAGER_COUNT_LIMIT
+ raise Error, '该专业管理员数量已达上限'
+ end
+
+ major_school.ec_major_school_users.create!(user: user)
+
+ user
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/create_score_levels_service.rb b/app/services/ecs/create_score_levels_service.rb
new file mode 100644
index 000000000..031bead1c
--- /dev/null
+++ b/app/services/ecs/create_score_levels_service.rb
@@ -0,0 +1,45 @@
+class Ecs::CreateScoreLevelsService < ApplicationService
+ include AcceptsNestedAttributesHelper
+
+ attr_reader :ec_course, :params
+
+ def initialize(ec_course, params)
+ @ec_course = ec_course
+ @params = params
+ end
+
+ def call
+ check_score_orderly!
+
+ deal_score_levels_params!
+
+ accepts_attributes = build_accepts_nested_attributes(ec_course, ec_course.ec_score_levels, params[:score_levels])
+
+ ec_course.assign_attributes(ec_score_levels_attributes: accepts_attributes)
+ ec_course.save!
+ ec_course
+ end
+
+ private
+
+ def check_score_orderly!
+ size = params[:score_levels].size
+ return if size < 2
+
+ score = nil
+ params[:score_levels].each_with_index do |item, index|
+ if score && item.score.to_i > score || (index + 1 == size && item.score.to_i >= score)
+ raise Error, '分数必须降序排列'
+ end
+
+ score = item.score.to_i
+ end
+ end
+
+ def deal_score_levels_params!
+ params[:score_levels].each_with_index do |item, index|
+ item[:position] = index + 1
+ item[:level] = item[:level].to_s.strip
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/create_training_objective_service.rb b/app/services/ecs/create_training_objective_service.rb
new file mode 100644
index 000000000..c3dc3c8a6
--- /dev/null
+++ b/app/services/ecs/create_training_objective_service.rb
@@ -0,0 +1,31 @@
+class Ecs::CreateTrainingObjectiveService < ApplicationService
+ include AcceptsNestedAttributesHelper
+
+ attr_reader :training_objective, :params
+
+ def initialize(ec_year, params)
+ @params = params
+ @training_objective = ec_year.ec_training_objective || ec_year.build_ec_training_objective
+ end
+
+ def call
+ training_objective.content = params[:content].to_s.strip
+
+ attributes = build_accepts_nested_attributes(
+ training_objective,
+ training_objective.ec_training_subitems,
+ params[:training_subitems],
+ &method(:training_subitem_param_handler)
+ )
+ training_objective.assign_attributes(ec_training_subitems_attributes: attributes)
+
+ training_objective.save!
+ training_objective
+ end
+
+ private
+
+ def training_subitem_param_handler(item)
+ item[:content] = item[:content].to_s.strip
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/import_course_service.rb b/app/services/ecs/import_course_service.rb
new file mode 100644
index 000000000..016336940
--- /dev/null
+++ b/app/services/ecs/import_course_service.rb
@@ -0,0 +1,31 @@
+class Ecs::ImportCourseService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :ec_year, :attachment
+
+ def initialize(ec_year, attachment_id)
+ @ec_year = ec_year
+ @attachment = Attachment.find_by(id: attachment_id)
+ end
+
+ def call
+ raise_import_error('文件不存在') if attachment.blank?
+
+ path = attachment.diskfile
+ excel = Ecs::ImportCourseExcel.new(path)
+
+ created_count = 0
+ EcCourse.bulk_insert(:name, :created_at, :updated_at) do |worker|
+ excel.read_each do |course_name|
+ next if ec_year.ec_courses.exists?(name: course_name)
+
+ worker.add(name: course_name)
+ created_count += 1
+ end
+ end
+
+ created_count
+ rescue BaseImportExcel::Error => ex
+ raise Error, ex.message
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/import_course_student_achievement_service.rb b/app/services/ecs/import_course_student_achievement_service.rb
new file mode 100644
index 000000000..b4c9e6bc5
--- /dev/null
+++ b/app/services/ecs/import_course_student_achievement_service.rb
@@ -0,0 +1,81 @@
+class Ecs::ImportCourseStudentAchievementService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :ec_year, :course_evaluation, :attachment
+
+ def initialize(course_evaluation, attachment_id)
+ @course_evaluation = course_evaluation
+ @ec_year = course_evaluation.ec_course.ec_year
+ @attachment = Attachment.find_by(id: attachment_id)
+ end
+
+ def call
+ raise Error, '文件不存在' if attachment.blank?
+ raise Error, '请先导入学生数据' if ec_year.ec_year_students.count.zero?
+
+ path = attachment.diskfile
+ excel = Ecs::ImportAchievementExcel.new(path)
+
+ ActiveRecord::Base.transaction do
+ course_evaluation.ec_student_achievements.delete_all
+
+ handler = excel.average_score_template? ? :average_score_handler : :detail_score_handler
+ EcStudentAchievement.bulk_insert(*achievement_columns) do |worker|
+ excel.read_each do |arr|
+ send(handler, worker, arr)
+ end
+ end
+
+ score_type = excel.average_score_template? ? :average : :detail
+ current_course_evaluation.update!(import_status: true, score_type: score_type)
+ end
+ rescue BaseImportExcel::Error => ex
+ raise Error, ex.message
+ end
+
+ private
+
+ def achievement_columns
+ %i[
+ score student_name student_number position ec_year_student_id
+ ec_course_evaluation_id ec_course_evaluation_subitem_id created_at updated_at
+ ]
+ end
+
+ def average_score_handler(worker, arr)
+ items_size = course_evaluation.ec_course_evaluation_subitems.size
+
+ ec_year.ec_year_students.find_each do |student|
+ course_evaluation.ec_course_evaluation_subitems.each_with_index do |evaluation_subitem, index|
+ course_evaluation.evaluation_count.times do |times|
+ score = arr[times * items_size + index].to_i # 共4个分项:第一次考核的第三项下标为 0 * 4 + 2,即 2
+ attrs = {
+ score: score, student_name: student.name, student_number: student.student_id,
+ position: times + 1, ec_year_student_id: student.id, ec_course_evaluation_id: course_evaluation.id,
+ ec_course_evaluation_subitem_id: evaluation_subitem.id
+ }
+ worker.add(attrs)
+ end
+ end
+ end
+ end
+
+ def detail_score_handler(worker, arr)
+ student = ec_year_students.find_by(student_id: arr[0].is_a?(Float) ? arr[0].to_i : arr[0].to_s.strip)
+ return if student.blank?
+
+ items_size = course_evaluation.ec_course_evaluation_subitems.size
+ course_evaluation.ec_course_evaluation_subitems.each_with_index do |evaluation_subitem, index|
+ course_evaluation.evaluation_count.times do |times|
+ # 因为0和1是学号和姓名,所以下标要 + 2
+ score = arr[times * items_size + 2 + index].to_i # 共4个分项:第一次考核的第三项下标为 0 * 4 + 2 + 2,即 arr[4]
+ attrs = {
+ score: score, student_name: student.name, student_number: student.student_id,
+ position: times + 1, ec_year_student_id: student.id, ec_course_evaluation_id: course_evaluation.id,
+ ec_course_evaluation_subitem_id: evaluation_subitem.id
+ }
+ worker.add(attrs)
+ end
+ end
+ end
+end
diff --git a/app/services/ecs/import_student_service.rb b/app/services/ecs/import_student_service.rb
new file mode 100644
index 000000000..daa384d58
--- /dev/null
+++ b/app/services/ecs/import_student_service.rb
@@ -0,0 +1,36 @@
+class Ecs::ImportStudentService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :ec_year, :attachment
+
+ def initialize(ec_year, attachment_id)
+ @ec_year = ec_year
+ @attachment = Attachment.find_by(id: attachment_id)
+ end
+
+ def call
+ raise_import_error('文件不存在') if attachment.blank?
+
+ path = attachment.diskfile
+ excel = Ecs::ImportStudentExcel.new(path)
+
+ success_count = 0
+ EcYearStudent.bulk_insert(:student_id, :name, :created_at, :updated_at) do |worker|
+ excel.read_each do |student_id, name|
+ success_count += 1
+
+ student = ec_year.ec_year_students.find_by(student_id: student_id)
+ if student.present?
+ student.update!(name: name)
+ next
+ end
+
+ worker.add(student_id: student_id, name: name)
+ end
+ end
+
+ success_count
+ rescue BaseImportExcel::Error => ex
+ raise Error, ex.message
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/link_course_service.rb b/app/services/ecs/link_course_service.rb
new file mode 100644
index 000000000..18ba8fd15
--- /dev/null
+++ b/app/services/ecs/link_course_service.rb
@@ -0,0 +1,17 @@
+class Ecs::LinkCourseService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :ec_course, :params
+
+ def initialize(ec_course, params)
+ @ec_course = ec_course
+ @params = params
+ end
+
+ def call
+ course = Course.find_by(id: params[:course_id])
+ raise Error, '课程不存在' if course.blank?
+
+ ec_course.ec_major_courses.create!(course: course)
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/query_course_evaluation_service.rb b/app/services/ecs/query_course_evaluation_service.rb
new file mode 100644
index 000000000..8c76da438
--- /dev/null
+++ b/app/services/ecs/query_course_evaluation_service.rb
@@ -0,0 +1,114 @@
+class Ecs::QueryCourseEvaluationService < ApplicationService
+ attr_reader :ec_course, :evaluation_status
+
+ def initialize(ec_course)
+ @ec_course = ec_course
+ @_course_achievement = 0
+ end
+
+ def course_targets
+ @_course_targets ||= calculate_course_targets
+ end
+
+ def course_achievement
+ course_targets
+
+ @_course_achievement.round(2)
+ end
+
+ def graduation_subitem_evaluations
+ student_scores = ec_course.ec_course_student_scores.joins(:ec_course_target).group(:ec_course_target_id)
+ student_scores = student_scores.select('AVG(score) as average_score, ec_course_target_id')
+ student_score_map = student_scores.group_by { |item| item.ec_course_target_id }
+
+ subitem_targets = ec_course.ec_graduation_subitem_course_targets
+ .includes(ec_graduation_subitem: :ec_graduation_requirement)
+
+ subitem_targets.group_by(&:ec_graduation_subitem_id).map do |_id, arr|
+ subitem = arr.first.ec_graduation_subitem
+
+ support = subitem.ec_course_supports.find_by(ec_course_id: ec_course.id)
+
+ weight = support.weights.to_f
+ objective_achievement = (weight * ec_course.ec_year.calculation_value.to_f).round(3)
+
+ target_total_rates = 0
+ reach_real_target = 0
+
+ arr.map(&:ec_course_target).uniq.each do |target|
+ target_total_rates += target.weight.to_f
+ student_score = student_score_map[target.id]
+
+ reach_real_target += student_score.average_score.to_f * target.weight.to_f if student_score
+ end
+
+ actually_achievement = target_total_rates.zero? ? 0 : (reach_real_target * weight) / (target_total_rates.round(3) * 100)
+
+ {
+ graduation_subitem_id: subitem.id,
+ content: subitem.content,
+ position: subitem.position,
+ graduation_requirement_position: subitem.ec_graduation_requirement.position,
+ weights: format('%.02f', weight),
+ support_course_target_ids: arr.map(&:ec_course_target_id),
+ objective_achievement: format('%.03f', objective_achievement),
+ actually_achievement: format('%.03f', actually_achievement),
+ status: achieve_status(objective_achievement, actually_achievement)
+ }
+ end
+ end
+
+ def score_levels_map
+ @_score_levels_map ||= begin
+ ec_course.ec_score_levels.each_with_object({}) do |level, obj|
+ obj[level.id.to_s] = level.as_json(only: %i[id position score level])
+ end
+ end
+ end
+
+ private
+
+ def calculate_course_targets
+ score_levels = ec_course.ec_score_levels.to_a
+
+ ec_course.ec_course_targets.includes(:ec_student_score_targets).map do |course_target|
+ data = course_target.as_json(only: %i[id position content standard_grade])
+ data[:weight] = course_target.weight
+
+ score_targets = course_target.ec_student_score_targets.to_a
+ data[:average_score] = score_targets.size.zero? ? 0 : score_targets.sum(&:score).fdiv(score_targets.size).round(2)
+ data[:maximum_score] = score_targets.max_by(&:score)&.score
+ data[:minimum_score] = score_targets.min_by(&:score)&.score
+ data[:actually_grade] = data[:average_score]
+
+ data[:status] = achieve_status(course_target.standard_grade, data[:average_score])
+
+ # 计算总评成绩
+ @_course_achievement += data[:average_score].to_f * course_target.weight.to_f
+
+ # 计算学生成绩分布区间
+ data[:score_levels] = score_levels.map do |score_level|
+ level_condition_proc =
+ if (score_level.position - 1).zero? # 第一区间
+ -> (score_target){ score_target.score >= score_level.score ? 1 : 0 }
+ elsif score_levels.position == score_levels.size # 末尾区间
+ -> (score_target){ score_target.score < score_level.score ? 1 : 0 }
+ else
+ # 中间区间
+ -> (score_target){ score_target.score >= score_level.score && score_target.score < score_targets[score_level.position - 1] ? 1 : 0 }
+ end
+
+ # 计算该成绩区间人数
+ count = score_targets.sum(&level_condition_proc)
+
+ { id: score_level.id, count: count }
+ end
+
+ data
+ end
+ end
+
+ def achieve_status(standard_value, real_value)
+ standard_value && real_value && real_value >= standard_value ? 'achieved' : 'not_achieved'
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/save_course_evaluation_service.rb b/app/services/ecs/save_course_evaluation_service.rb
new file mode 100644
index 000000000..1e5af8756
--- /dev/null
+++ b/app/services/ecs/save_course_evaluation_service.rb
@@ -0,0 +1,34 @@
+class Ecs::SaveCourseEvaluationService < ApplicationService
+ include AcceptsNestedAttributesHelper
+
+ attr_reader :ec_course, :course_evaluation, :params
+
+ def initialize(course_evaluation, params)
+ @course_evaluation = course_evaluation
+ @ec_course = course_evaluation.ec_course
+ @params = params
+ end
+
+ def call
+ Ecs::SaveCourseEvaluationForm.new(params).validate!
+
+ subitem_attributes = build_accepts_nested_attributes(
+ course_evaluation,
+ course_evaluation.ec_course_evaluation_subitems,
+ params.delete(:course_evaluation_subitems),
+ &method(:course_evaluation_subitem_param_handler!)
+ )
+
+ course_evaluation.assign_attributes(params)
+ course_evaluation.assign_attributes(ec_course_evaluation_subitems_attributes: subitem_attributes)
+
+ course_evaluation.save!
+ course_evaluation
+ end
+
+ private
+
+ def course_evaluation_subitem_param_handler!(item)
+ item[:name] = item[:name].to_s.strip
+ end
+end
diff --git a/app/services/ecs/save_graduation_course_supports_service.rb b/app/services/ecs/save_graduation_course_supports_service.rb
new file mode 100644
index 000000000..bfbdb997f
--- /dev/null
+++ b/app/services/ecs/save_graduation_course_supports_service.rb
@@ -0,0 +1,23 @@
+class Ecs::SaveGraduationCourseSupportsService < ApplicationService
+ include AcceptsNestedAttributesHelper
+
+ attr_reader :graduation_subitem, :params
+
+ def initialize(graduation_subitem, params)
+ @params = params
+ @graduation_subitem = graduation_subitem
+ end
+
+ def call
+ Ecs::SaveGraduationCourseSupportForm.new(params).validate!
+
+ accepts_attributes = build_accepts_nested_attributes(
+ graduation_subitem, graduation_subitem.ec_course_supports,
+ params[:course_supports]
+ )
+
+ graduation_subitem.assign_attributes(ec_course_supports_attributes: accepts_attributes)
+ graduation_subitem.save!
+ graduation_subitem
+ end
+end
\ No newline at end of file
diff --git a/app/services/ecs/save_graduation_requiremente_service.rb b/app/services/ecs/save_graduation_requiremente_service.rb
new file mode 100644
index 000000000..043a9bca7
--- /dev/null
+++ b/app/services/ecs/save_graduation_requiremente_service.rb
@@ -0,0 +1,69 @@
+class Ecs::SaveGraduationRequirementeService < ApplicationService
+ include AcceptsNestedAttributesHelper
+
+ attr_reader :ec_year, :graduation_requirement, :params
+
+ def initialize(graduation_requirement, params)
+ @graduation_requirement = graduation_requirement
+ @ec_year = graduation_requirement.ec_year
+ @params = params
+ end
+
+ def call
+ set_graduation_subitems_position!
+
+ graduation_requirement.position = params[:position].presence || ec_year.ec_graduation_requirements.count + 1
+ graduation_requirement.content = params[:content].to_s.strip
+
+ attributes = build_accepts_nested_attributes(
+ graduation_requirement,
+ graduation_requirement.ec_graduation_subitems,
+ params[:graduation_subitems],
+ &method(:graduation_subitem_params_handler!)
+ )
+
+ ActiveRecord::Base.transaction do
+ graduation_requirement.assign_attributes(ec_graduation_subitems_attributes: attributes)
+
+ resort_graduation_requirements_position!
+
+ graduation_requirement.save!
+ end
+
+ graduation_requirement
+ end
+
+ private
+
+ def set_graduation_subitems_position!
+ params[:graduation_subitems].each_with_index do |item, index|
+ item[:position] = index + 1
+ end
+ end
+
+ def resort_graduation_requirements_position!
+ # 新增时
+ if graduation_requirement.new_record?
+ # 插入数据时只需要后移
+ ec_year.ec_graduation_requirements.where('position >= ?', graduation_requirement.position)
+ .update_all('position = position + 1')
+ return
+ end
+
+ # 编辑时
+ new_position = graduation_requirement.position
+ old_position = graduation_requirement.position_in_database
+
+ if new_position > old_position # 前移
+ ec_year.ec_graduation_requirements.where('position <= ? AND position > ?', new_position, old_position)
+ .update_all('position = position - 1')
+ elsif new_position < old_position # 后移
+ ec_year.ec_graduation_requirements.where('position >= ? AND position < ?', new_position, old_position)
+ .update_all('position = position + 1')
+ end
+ end
+
+ def graduation_subitem_params_handler!(subitem)
+ subitem[:content] = subitem[:content].to_s.strip
+ end
+end
diff --git a/app/services/exercise_user_pdf_service.rb b/app/services/exercise_user_pdf_service.rb
new file mode 100644
index 000000000..5f92aee9c
--- /dev/null
+++ b/app/services/exercise_user_pdf_service.rb
@@ -0,0 +1,90 @@
+class ExerciseUserPdfService
+ include ExercisesHelper
+ include ApplicationHelper
+ include StudentWorksHelper
+
+ attr_reader :exercise, :ex_user
+
+ def initialize(exercise, ex_user)
+ @exercise = exercise
+ @ex_user = ex_user
+ @ex_user_user = @ex_user.user
+ @course = @exercise.course
+ end
+
+ def filename
+ user_course = @course.course_members.find_by(user_id:@ex_user_user.id).course_group_name
+ exercise_user_name = user_course + "_" + exercise.exercise_name + "_" + @ex_user_user.real_name
+ "#{exercise_user_name.strip}.pdf"
+ end
+
+ def prepare_binding
+ load_data
+
+ binding
+ end
+
+ def ex_pdf
+ generate_pdf
+ end
+
+ private
+
+ def generate_pdf
+ file = File.open(Rails.root.join('app/templates/exercise_export/exercise_user.html.erb'))
+ html = ERB.new(file.read).result(prepare_binding)
+
+ kit = PDFKit.new(html)
+ base_css = %w(app/templates/exercise_export/exercise_export.css)
+ base_css.each { |css| kit.stylesheets << Rails.root.join(css) }
+ #-----正式需删掉
+ # aa = File.open(Rails.root.join("public/123.html"),"w+")
+ # aa.syswrite(kit.source)
+ #正式需删掉-------
+
+ file = Tempfile.new(filename)
+ kit.to_pdf(file.path)
+ file
+ end
+
+ def load_data
+ @exercise_questions = exercise.exercise_questions
+ @exercise_ques_count = @exercise_questions.count # 全部的题目数
+ @exercise_ques_scores = @exercise_questions.pluck(:question_score).sum
+
+ #单选题的数量及分数
+ exercise_single_ques = @exercise_questions.find_by_custom("question_type",0)
+ @exercise_single_ques_count = exercise_single_ques.all.count
+ @exercise_single_ques_scores = exercise_single_ques.pluck(:question_score).sum
+
+ #多选题的数量及分数
+ exercise_double_ques = @exercise_questions.find_by_custom("question_type",1)
+ @exercise_double_ques_count = exercise_double_ques.all.count
+ @exercise_double_ques_scores = exercise_double_ques.pluck(:question_score).sum
+
+ # 判断题数量及分数
+ exercise_ques_judge = @exercise_questions.find_by_custom("question_type",2)
+ @exercise_ques_judge_count = exercise_ques_judge.all.count
+ @exercise_ques_judge_scores = exercise_ques_judge.pluck(:question_score).sum
+
+ #填空题数量及分数
+ exercise_ques_null = @exercise_questions.find_by_custom("question_type",3)
+ @exercise_ques_null_count = exercise_ques_null.all.count
+ @exercise_ques_null_scores = exercise_ques_null.pluck(:question_score).sum
+
+ #简答题数量及分数
+ exercise_ques_main = @exercise_questions.find_by_custom("question_type",4)
+ @exercise_ques_main_count = exercise_ques_main.all.count
+ @exercise_ques_main_scores = exercise_ques_main.pluck(:question_score).sum
+
+ #实训题数量及分数
+ exercise_ques_shixun = @exercise_questions.find_by_custom("question_type",5)
+ @exercise_ques_shixun_count = exercise_ques_shixun.all.count
+ @exercise_ques_shixun_scores = exercise_ques_shixun.pluck(:question_score).sum
+
+ challenge_ids = @exercise_questions.joins(:exercise_shixun_challenges).pluck("exercise_shixun_challenges.challenge_id")
+
+ get_each_student_exercise(exercise.id,@exercise_questions,@ex_user_user.id)
+ @games = @exercise_user.user.games.ch_games(challenge_ids)
+ end
+end
\ No newline at end of file
diff --git a/app/services/export_exercises_service.rb b/app/services/export_exercises_service.rb
new file mode 100644
index 000000000..12b5501f9
--- /dev/null
+++ b/app/services/export_exercises_service.rb
@@ -0,0 +1,35 @@
+class ExportExercisesService
+ include ExercisesHelper
+ include StudentWorksHelper
+ attr_reader :exercise, :ex_users
+
+ def initialize(exercise, ex_users)
+ @exercise = exercise
+ @ex_users = ex_users
+ end
+
+ def filename
+ exercise_export_name = exercise.user.real_name + "_" + exercise.exercise_name + "_" + Time.now.strftime('%Y%m%d_%H%M%S')
+ "#{exercise_export_name.strip}.zip"
+ end
+
+ def ex_zip
+ zip_file = Tempfile.new(filename)
+ pdfs = []
+ Zip::File.open(zip_file.path, Zip::File::CREATE) do |zip|
+ ex_users.each do |ex_user|
+ export = ExerciseUserPdfService.new(exercise, ex_user)
+ pdf = export.ex_pdf
+ pdfs << pdf
+ begin
+ zip.add(export.filename, pdf.path)
+ rescue => ex
+ Rails.logger.error(ex.message)
+ zip.get_output_stream('FILE_NOTICE.txt'){|os| os.write("文件重复:#{export.filename}") }
+ end
+ end
+ zip_file
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/export_shixun_report_service.rb b/app/services/export_shixun_report_service.rb
new file mode 100644
index 000000000..8358d422e
--- /dev/null
+++ b/app/services/export_shixun_report_service.rb
@@ -0,0 +1,54 @@
+class ExportShixunReportService
+ include ApplicationHelper
+ include StudentWorksHelper
+
+ attr_reader :homework, :work
+
+ def initialize(homework, work)
+ @homework = homework
+ @work = work
+ end
+
+ def filename
+ @_filename ||= "#{homework.name}-#{work.user.user_extension&.student_id}-#{work.user.real_name}.pdf".gsub(' ', '-').gsub('/', '_')
+ end
+
+ def prepare_binding
+ load_data
+
+ binding
+ end
+
+ def to_pdf
+ @_pdf ||= generate_pdf
+ end
+
+ private
+
+ def generate_pdf
+ file = File.open(Rails.root.join('app/templates/shixun_work/shixun_work.html.erb'))
+ html = ERB.new(file.read).result(prepare_binding)
+ kit = PDFKit.new(html)
+
+ base_css = %w(app/templates/shared/main.css app/templates/shixun_work/shixun_work.css app/templates/shared/codemirror.css)
+ base_css.each { |css| kit.stylesheets << Rails.root.join(css) }
+
+ file = Tempfile.new(filename)
+ kit.to_pdf(file.path)
+ file
+ end
+
+ def load_data
+ @course = homework.course
+ @user = @work.user
+ @shixun = homework.shixuns.take
+ @games = @work.myshixun.games.includes(:challenge, :game_codes,:outputs) if @work.myshixun
+
+ # 用户最大评测次数
+ @user_evaluate_count = @games.inject(0){|sum, g| sum + g.outputs.pluck(:query_index).first } if @games
+ # 图形效率图的数据
+ @echart_data = student_efficiency(homework, @work)
+ @myself_eff = @echart_data[:efficiency_list].find { |item| item.last == @user.id }
+ @myself_consume = @echart_data[:consume_list].find { |item| item.last == @user.id }
+ end
+end
\ No newline at end of file
diff --git a/app/services/git_service.rb b/app/services/git_service.rb
new file mode 100644
index 000000000..4b6bf17ef
--- /dev/null
+++ b/app/services/git_service.rb
@@ -0,0 +1,43 @@
+#coding=utf-8
+#
+# 文档在 https://www.showdoc.cc/127895880302646?page_id=1077512172693249
+#
+class GitService
+
+ class << self
+
+ ['add_repository', 'fork_repository', 'delete_repository', 'file_tree', 'update_file', 'file_content', 'commits'].each do |method|
+ define_method method do |params|
+ post(method, params)
+ end
+ end
+
+ private
+
+ def root_url
+ new_git_address = EduSetting.find_by_name('git_address_domain').try(:value)
+ raise 'error: new_git_address not configuration' unless new_git_address.present?
+ new_git_address
+ end
+
+ def logger
+ Rails.logger
+ end
+
+ def post(action, params)
+ uri = URI.parse("#{root_url}/api/#{action}")
+ https = Net::HTTP.new(uri.host, uri.port)
+ https.use_ssl = root_url.start_with?('https')
+ req = Net::HTTP::Post.new(uri.path, initheader = {'Content-Type' => 'text/plain;charset=utf-8'})
+ req.body = params.to_json
+ res = https.request(req)
+ body = res.body
+ logger.info("--uri_exec: .....res is #{body}")
+ content = JSON.parse(body)
+ #raise content["msg"] if content["code"] != 0
+
+ content["data"]
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/homeworks_service.rb b/app/services/homeworks_service.rb
new file mode 100644
index 000000000..536d62c66
--- /dev/null
+++ b/app/services/homeworks_service.rb
@@ -0,0 +1,275 @@
+class HomeworksService
+
+ # 创建实训作业
+ def create_homework shixun, course, category, current_user
+ ActiveRecord::Base.transaction do
+ homework = HomeworkCommon.new(name: shixun.name, description: shixun.description, homework_type: 4,
+ user_id: current_user.id, course_id: course.id,
+ course_second_category_id: category.try(:id).to_i)
+
+ homework_detail_manual = HomeworkDetailManual.new
+ homework.homework_detail_manual = homework_detail_manual
+
+ if homework.save!
+ homework_detail_manual.save! if homework_detail_manual
+ HomeworkCommonsShixun.create!(homework_common_id: homework.id, shixun_id: shixun.id)
+ HomeworksService.new.create_shixun_homework_cha_setting(homework, shixun)
+ HomeworksService.new.create_works_list(homework, course)
+ end
+ homework
+ end
+ end
+
+ #更新实训作业的状态
+ def update_shixun_work_status homework
+ shixun = homework.shixuns.first
+ student_works = homework.student_works.unfinished
+ homework_challenge_settings = homework.homework_challenge_settings
+ challeng_ids = homework_challenge_settings.map(&:challenge_id)
+ # 取已发布的作品
+ if homework.unified_setting
+ student_works = student_works
+ else
+ setting = homework.homework_group_settings.group_published
+ if setting.blank?
+ student_works = student_works.none
+ else
+ users = homework.course.course_members.course_find_by_ids("course_group_id",setting.map(&:course_group_id))
+ student_works = student_works.homework_by_user(users.map(&:user_id))
+ end
+ end
+ # 已发布作品且状态为未提交的作品 如果有开启过实训则更新状态
+ myshixuns = Myshixun.where(:shixun_id => shixun.id, :user_id => student_works.map(&:user_id))
+ myshixuns.each do |myshixun|
+ work = student_works.homework_by_user(myshixun.user_id).first
+ setting_time = homework.homework_group_setting myshixun.user_id
+ games = myshixun.games.where(:challenge_id => challeng_ids)
+ myshixun_endtime = games.select{|game| game.status == 2}.size == games.size ? games.map(&:end_time).max : nil
+ compelete_status = 0
+ if myshixun_endtime.present? && myshixun_endtime < setting_time.end_time
+ if myshixun_endtime < setting_time.publish_time
+ compelete_status = 2
+ else
+ compelete_status = 1
+ end
+ end
+ if setting_time.end_time > Time.now
+ work.update_attributes(:work_status => 1, :late_penalty => 0, :commit_time => myshixun.updated_at, :update_time => myshixun.updated_at, :myshixun_id => myshixun.id, :compelete_status => compelete_status)
+ else
+ work.update_attributes(:work_status => ((myshixun.is_complete? && (myshixun.done_time < setting_time.end_time)) ? 1 : 2), :late_penalty => (myshixun.is_complete? && (myshixun.done_time < setting_time.end_time) ? 0 : homework.late_penalty), :commit_time => myshixun.updated_at, :update_time => myshixun.updated_at, :myshixun_id => myshixun.id, :compelete_status => compelete_status)
+ end
+ set_shixun_final_score work
+ end
+ # 更新所有学生的效率分
+ HomeworksService.new.update_student_eff_score HomeworkCommon.where(:id => homework.id).first
+ end
+
+ # 实训作业的评分
+ def set_shixun_final_score student_work
+ homework = student_work.homework_common
+ answer_open_evaluation = homework.homework_detail_manual.answer_open_evaluation
+ myshixun = student_work.myshixun
+ if student_work.work_status != 0 && myshixun.present?
+ final_score = 0
+ compelete = true
+ max_endtime = ""
+ user_total_score = 0
+ pass_consume_time = 0
+ homework.homework_challenge_settings.each do |setting|
+ game = myshixun.games.find_by(challenge_id: setting.challenge_id, status: 2)
+ unless game.nil?
+ pass_consume_time += (game.cost_time / 60.0).to_f
+ user_total_score += game.final_score.to_i < 0 ? 0 : game.challenge.score.to_i
+ adjust_score = student_work.challenge_work_scores.where(challenge_id: setting.challenge_id).last
+ final_score += adjust_score.present? ? adjust_score.score : (answer_open_evaluation ? setting.score : (game.final_score >= 0 ? setting.score : 0))
+ max_endtime = max_endtime == "" ? game.end_time : (game.end_time > max_endtime ? game.end_time : max_endtime)
+ else
+ compelete = false
+ end
+ end
+
+ if compelete && max_endtime != ""
+ homework = student_work.homework_common
+ setting_time = homework.homework_group_setting student_work.user_id
+ if setting_time.publish_time.present? && setting_time.end_time.present?
+ if max_endtime < setting_time.publish_time
+ student_work.compelete_status = 2
+ else
+ if max_endtime < setting_time.end_time || (homework.allow_late && (homework.course.end_date.nil? ||
+ max_endtime < homework.course.end_date.end_of_day))
+ student_work.compelete_status = 1
+ student_work.cost_time = max_endtime.to_i - setting_time.publish_time.to_i
+ else
+ student_work.compelete_status = 0
+ end
+ end
+ end
+
+ efficiency = (pass_consume_time == 0 ? 0 : Math.log((user_total_score / pass_consume_time.to_f) + 1.0))
+ student_work.efficiency = efficiency < 0 ? 0 : format("%.2f", efficiency)
+
+ if homework.work_efficiency
+ if homework.max_efficiency < student_work.efficiency
+ # homework.max_efficiency = student_work.efficiency
+ homework.update_column("max_efficiency", homework.max_efficiency)
+ end
+ # eff_score = homework.max_efficiency == 0 ? 0 : student_work.efficiency / homework.max_efficiency * homework.eff_score
+ # student_work.eff_score = format("%.2f", eff_score)
+ # else
+ # student_work.eff_score = 0
+ end
+ elsif !compelete
+ student_work.compelete_status = 0
+ end
+ student_work.final_score = format("%.2f", final_score.to_f)
+ student_work.late_penalty = student_work.work_status == 1 ? 0 : homework.late_penalty
+ score = student_work.final_score + student_work.eff_score - student_work.late_penalty
+ student_work.work_score = format("%.2f", score < 0 ? 0 : score.to_f) unless student_work.ultimate_score
+ student_work.save!
+ end
+ end
+
+ # 计算实训作品学生的效率分
+ def update_student_eff_score homework
+ if homework.work_efficiency && homework.max_efficiency != 0
+ homework.student_works.where("compelete_status != 0").each do |student_work|
+ eff_score = student_work.efficiency / homework.max_efficiency * homework.eff_score
+ student_work.eff_score = format("%.2f", eff_score)
+ student_work.late_penalty = student_work.work_status == 1 ? 0 : homework.late_penalty
+ unless student_work.ultimate_score
+ work_score = student_work.final_score.to_f + student_work.eff_score - student_work.late_penalty
+ student_work.work_score = format("%.2f", work_score < 0 ? 0 : work_score)
+ end
+ student_work.save!
+ end
+ else
+ homework.student_works.where("compelete_status != 0").each do |student_work|
+ student_work.eff_score = 0
+ student_work.late_penalty = student_work.work_status == 1 ? 0 : homework.late_penalty
+ unless student_work.ultimate_score
+ work_score = student_work.final_score.to_f + student_work.eff_score - student_work.late_penalty
+ student_work.work_score = format("%.2f", work_score < 0 ? 0 : work_score)
+ end
+ student_work.save!
+ end
+ end
+ end
+
+ # 用户评测时更新实训作业成绩
+ def update_myshixun_work_score myshixun
+ ActiveRecord::Base.transaction do
+ student_works = myshixun.student_works.where(user_id: myshixun.user_id)
+ #logger.info("#############student_works_count: #{student_works.count}")
+ if student_works.count > 0
+ student_works.each do |work|
+ homework = work.homework_common
+ #logger.info("#############member_course_group_id: #{member.try(:course_group_id)}")
+ setting_time = homework.homework_group_setting work.user_id
+ if setting_time.end_time.present? && (setting_time.end_time > Time.now || (homework.allow_late && !homework.course.is_end))
+ #logger.info("#############setting_time: #{setting_time.end_time}")
+
+ user_total_score = 0
+ pass_consume_time = 0
+ final_score = 0
+ homework.homework_challenge_settings.each do |setting|
+ game = myshixun.games.where(:challenge_id => setting.challenge_id, :status => 2).first
+ unless game.nil?
+ pass_consume_time += (game.cost_time / 60.0).to_f
+ user_total_score += game.final_score.to_i < 0 ? 0 : game.challenge.score.to_i
+ adjust_score = work.challenge_work_scores.where(:challenge_id => setting.challenge_id).last
+ final_score += adjust_score.present? ? adjust_score.score : (homework.homework_detail_manual.answer_open_evaluation ? setting.score : (game.final_score >= 0 ? setting.score : 0))
+ end
+ end
+ if work.work_status == 0
+ is_complete = myshixun.is_complete? && (myshixun.done_time < setting_time.end_time)
+ work.work_status = setting_time.end_time > Time.now ? 1 : (is_complete ? 1 : 2)
+ work.late_penalty = setting_time.end_time > Time.now ? 0 : (is_complete ? 0 : homework.late_penalty)
+ work.commit_time = myshixun.created_at > setting_time.publish_time ? setting_time.publish_time : myshixun.created_at
+ work.myshixun_id = myshixun.id
+ end
+
+ games = myshixun.games.where(:challenge_id => homework.homework_challenge_settings.map(&:challenge_id))
+ myshixun_endtime = games.select{|game| game.status == 2}.size == games.size ? games.map(&:end_time).max : nil
+ if myshixun_endtime.present?
+ min_efficiency_changed = min_efficiency_changed.present? ? min_efficiency_changed : false
+ work.compelete_status = 1
+ work.cost_time = myshixun_endtime.to_i - setting_time.publish_time.to_i
+
+ efficiency = (pass_consume_time == 0 ? 0 : Math.log((user_total_score / pass_consume_time.to_f) + 1.0))
+ work.efficiency = format("%.2f", efficiency)
+
+ # 如果作业的最大效率值有变更则更新所有作品的效率分
+ if homework.work_efficiency && homework.max_efficiency < work.efficiency
+ homework.update_column("max_efficiency", homework.max_efficiency)
+ end
+ end
+
+ work.update_time = Time.now
+
+ work.final_score = final_score
+ score = work.final_score + work.eff_score - work.late_penalty
+ work.work_score = format("%.2f",(score < 0 ? 0 : score).to_f) unless work.ultimate_score
+ #logger.info("#############work_score: #{score}")
+ work.save!
+ end
+ end
+ end
+ end
+ end
+
+ # 用户开启实训时更新作品状态
+ def update_myshixun_work_status myshixun
+ student_works = StudentWork.find_by_sql("SELECT sw.* FROM student_works sw, homework_commons_shixuns hcs WHERE
+ sw.user_id = #{myshixun.user_id} AND sw.`homework_common_id` = hcs.`homework_common_id`
+ AND hcs.`shixun_id` = #{myshixun.shixun_id} and sw.work_status = 0")
+ Rails.logger.info("#############student_works_count: #{student_works.count}")
+ if student_works.count > 0
+ student_works.each do |work|
+ homework = work.homework_common
+ setting_time = homework.homework_group_setting work.user_id
+ if setting_time.end_time.present? &&
+ (setting_time.end_time > Time.now || (homework.allow_late && homework.late_time && homework.late_time > Time.now))
+
+ work.work_status = setting_time.end_time > Time.now ? 1 : 2
+ work.late_penalty = setting_time.end_time > Time.now ? 0 : homework.late_penalty
+ work.update_time = Time.now
+ work.commit_time = Time.now
+ work.myshixun_id = myshixun.id
+ work.save!
+ end
+ end
+ end
+ end
+
+ # 实训作业的关卡设置
+ def create_shixun_homework_cha_setting homework, shixun
+ if shixun.present?
+ sum_score = 0
+ shixun.challenges.each_with_index do |challeng, index|
+ if index < shixun.challenges.length - 1
+ score = ((100.0 / shixun.challenges.length) * 100).floor / 100.0
+ sum_score += score
+ else
+ score = 100 - sum_score
+ end
+ HomeworkChallengeSetting.create!(homework_common_id: homework.id, challenge_id: challeng.id,
+ shixun_id: shixun.id, score: score)
+ end
+ end
+ end
+
+ # 为课堂学生创建作品
+ def create_works_list homework, course
+ if course.present? && CourseMember.students(course).size > 0
+ str = ""
+ CourseMember.students(course).each do |student|
+ str += "," if str != ""
+ str += "(#{homework.id},#{student.user_id}, '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}')"
+ end
+ if str != ""
+ sql = "insert into student_works (homework_common_id, user_id, created_at, updated_at) values" + str
+ ActiveRecord::Base.connection.execute sql
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/review_service.rb b/app/services/review_service.rb
new file mode 100644
index 000000000..7602aa51b
--- /dev/null
+++ b/app/services/review_service.rb
@@ -0,0 +1,231 @@
+#coding=utf-8
+#
+require 'net/http'
+require 'uri'
+
+class ReviewService
+ @review_server_url = EduSetting.find_by_name('review_server_url').try(:value)
+
+
+ def self.logger
+ @logger ||= Logger.new(File.join(Rails.root, "log", "review.log"), 'daily')
+ end
+
+ def self.root_url
+ @review_server_url.presence || 'http://10.9.79.221:80'
+ end
+
+ def self.postForm(action, map)
+ Rails.logger.info("##################------action: #{action}")
+ Rails.logger.info("##################------action: #{map}")
+ begin
+ uri = URI.parse("#{root_url}/api/v1/#{action}")
+
+ Rails.logger.info "请求url: #{uri}"
+ Rails.logger.info "参数: "
+ map.each do |k, v|
+ Rails.logger.info "#{k}=>#{v}"
+ end
+
+ res = Net::HTTP.post_form uri, map
+ body = res.body
+ Rails.logger.info("返回: #{body}")
+ content = JSON.parse(body)
+ content
+ rescue => e
+ Rails.logger.error("post #{action}: exception #{e.message}")
+ end
+ end
+
+ def self.postJson(action, params)
+ begin
+ uri = URI.parse("#{root_url}/api/v1/#{action}")
+ https = Net::HTTP.new(uri.host, uri.port)
+ https.use_ssl = root_url.start_with?('https')
+ req = Net::HTTP::Post.new(uri.path, initheader = {'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8'})
+ req.body = params.to_json
+ res = https.request(req)
+ body = res.body
+ logger.info("--uri_exec: .....res is #{body}")
+ content = JSON.parse(body)
+ raise content["msg"] if content["code"] != 0
+
+ content["data"]
+ rescue Exception => e
+ logger.error("--uri_exec: exception #{e.message}")
+ end
+ end
+
+
+ class CheckResponse
+ attr_accessor :status, :msg, :query_id
+
+ def initialize(status, msg, query_id)
+ @status = status
+ @msg = msg
+ @query_id = query_id
+ end
+ end
+
+ #查重
+ # 参数说明
+ # user_list [
+ # {
+ # user_id: 1,
+ # code_info: [
+ # {
+ # "path": "src/step1/HelloWorld.java",
+ # "content: "ADAGWDSSAS22",
+ # "passed_time": "2016-01-08 00:00:00"
+ # },
+ # ]
+ # },
+ # ]
+ #
+ # 返回说明
+ #
+ # {
+ # code: 0, 0 成功,其他失败
+ # msg: '', //失败时的错误消息
+ # query_id: '123123123', //本次查重的查询号
+ # }
+ #
+ #
+ def self.check(user_list = [], language='java')
+ File.open("/tmp/check_codes_#{Time.now}.json", 'w+') do |f|
+ f.write(user_list.to_json)
+ end
+
+ data = postForm('check_codes', {user_data: user_list.to_json, language: language})
+ if data.nil?
+ return CheckResponse.new(-1, "系统调用出错", "")
+ end
+ Rails.logger.info("@@@@@@@@@@@@@@@@------------#{data}")
+ CheckResponse.new(data["status"], data["msg"], data["query_id"])
+ end
+
+
+ class QueryResultResponse
+ UserList = Struct.new(:user_id, :target_user_id, :origin_path, :target_path, :rate)
+ attr_accessor :status, :msg, :user_lists
+
+ def initialize(status, msg, user_lists)
+ @status, @msg, @user_lists = status, msg, []
+
+ if user_lists.present?
+ user_lists.each do |info|
+ user = find_by_user_id_and_origin_path(info["user_id"], info["path"]["origin"])
+ if !user.present?
+ add_user_list(info)
+ elsif user.rate < info["rate"]
+ set_user_list(user, info)
+ end
+ end
+ end
+ end
+
+ def set_user_list(user, obj)
+ user.user_id = obj["user_id"]
+ user.target_user_id = obj["target_user_id"]
+ user.origin_path = obj["path"]["origin"]
+ user.target_path = obj["path"]["target"]
+ user.rate = obj["rate"]
+ end
+
+ def add_user_list(obj)
+ user_list = UserList.new
+ set_user_list(user_list, obj)
+ @user_lists << user_list
+ end
+
+ def find_by_user_id_and_origin_path(user_id, origin_path)
+ self.user_lists.each do |user|
+ return user if user.user_id == user_id and user.origin_path == origin_path
+ end
+ nil
+ end
+
+ end
+
+
+ class QueryDetailResultResponse
+ CodeInfo = Struct.new(:origin_path, :target_path, :origin_content, :target_content, :target_user_id, :rate)
+
+
+ attr_accessor :status, :msg, :code_info
+
+ def initialize(status, msg, code_info)
+ @status, @msg, @code_info = status, msg, []
+
+ if code_info.present?
+ code_info.each do |code|
+ info = find_by_path(code["path"]["origin"])
+ if !info.present?
+ add_code_info(code)
+ elsif info.rate < code["rate"]
+ set_code_info(info, code)
+ end
+ end
+ end
+ end
+
+ def set_code_info(info, obj)
+ info.origin_path = obj["path"]["origin"]
+ info.target_path = obj["path"]["target"]
+ info.origin_content = obj["content"]["origin"]
+ info.target_content = obj["content"]["target"]
+ info.target_user_id = obj["target_user_id"]
+ info.rate = obj["rate"]
+ end
+
+ def add_code_info(obj)
+ info = CodeInfo.new
+ set_code_info(info, obj)
+ @code_info << info
+ end
+
+ def find_by_path(path)
+ self.code_info.each do |code_info|
+ return code_info if code_info.origin_path == path
+ end
+ nil
+ end
+ end
+
+
+ #查询查重的结果
+ #
+ # 输入参数
+ # query_id 待查询的ID
+ # user_id 传入则查详情
+ #
+ # 文档参考 https://www.showdoc.cc/127895880302646?page_id=1271144663669096
+ #
+ def self.query_result(opt = {})
+ unless opt[:query_id].present?
+ logger.error "query_id没有传"
+ return
+ end
+
+ if opt[:user_id].present?
+ data = postForm('check_info', query_id: opt[:query_id], user_id: opt[:user_id])
+ if data.nil?
+ return QueryDetailResultResponse.new(-1, "系统调用出错", [])
+ end
+
+ response = QueryDetailResultResponse.new(data['status'], data['msg'], data["code_info"])
+ return response
+
+ else
+ data = postForm('check_lists', query_id: opt[:query_id])
+ if data.nil?
+ return CheckResponse.new(-1, "系统调用出错", "")
+ end
+
+ response = QueryResultResponse.new(data['status'], data['msg'], data["user_lists"])
+ return response
+
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/reward_experience_service.rb b/app/services/reward_experience_service.rb
new file mode 100644
index 000000000..70b47d1a3
--- /dev/null
+++ b/app/services/reward_experience_service.rb
@@ -0,0 +1,24 @@
+class RewardExperienceService
+ attr_reader :user, :attrs
+
+ def initialize(user, **attrs)
+ @user = user
+ @attrs = attrs.slice(*%i[container_id container_type score])
+ end
+
+ def call
+ return if user.experiences.exists?(attrs.except(:score))
+
+ ActiveRecord::Base.transaction do
+ experience = user.experiences.create!(attrs)
+
+ user.increment!(:experience, experience.score)
+
+ experience
+ end
+ end
+
+ def self.call(user, **attrs)
+ new(user, attrs).call
+ end
+end
\ No newline at end of file
diff --git a/app/services/reward_grade_service.rb b/app/services/reward_grade_service.rb
new file mode 100644
index 000000000..7642e1967
--- /dev/null
+++ b/app/services/reward_grade_service.rb
@@ -0,0 +1,18 @@
+class RewardGradeService < ApplicationService
+ attr_reader :user, :attrs
+
+ def initialize(user, **attrs)
+ @user = user
+ @attrs = attrs.slice(*%i[container_id container_type score])
+ end
+
+ def call
+ return if user.grades.exists?(attrs)
+
+ ActiveRecord::Base.transaction do
+ grade = user.grades.create!(attrs)
+
+ user.increment!(:grade, grade.score)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/shixuns_service.rb b/app/services/shixuns_service.rb
new file mode 100644
index 000000000..83d61b352
--- /dev/null
+++ b/app/services/shixuns_service.rb
@@ -0,0 +1,156 @@
+# encoding=utf-8
+class ShixunsService
+ include ApplicationHelper
+ include GamesHelper
+ LIMIT = 10
+
+ # reuturn star_info打星情况
+ def show_shixun params, current_user
+ shixun = Shixun.select([:id, :identifier, :name, :fork_from, :user_id, :myshixuns_count, :trainee, :can_copy, :status, :propaedeutics]).find_by_identifier(params[:identifier])
+ star_info = shixun.shixun_preference_info
+ image_url = url_to_avatar(current_user)
+ username = current_user.try(:show_name)
+ is_certification_teacher = current_user.is_certification_teacher
+ manager = current_user.manager_of_shixun?(shixun, current_user)
+ owner = shixun.owner
+ watched = owner.watched_by?(current_user)
+ myshixun = Myshixun.select([:id, :user_id, :identifier, :shixun_id, :status]).where(:user_id => current_user, :shixun_id => shixun.id).first
+ if shixun.fork_from
+ fork_shixun = Shixun.select([:id, :name, :user_id]).find(shixun.fork_from)
+ fork_info = {:name => fork_shixun.name, :username => fork_shixun.owner.try(:show_name)}
+ end
+ current_user = {:image_url => image_url, :username => username, :is_certification_teacher=> is_certification_teacher, :manager => manager,
+ :admin => current_user.admin?, :mail => current_user.try(:mail), :login => current_user.try(:login)}
+ creator = {:owner_id => owner.id, :image_url => url_to_avatar(owner), :username => owner.show_name, :watching => owner.watcher_users.count,
+ :shixun_count => owner.shixuns.count, :login => owner.login}
+ fork_count = Shixun.where(:fork_from => shixun.id).count
+
+ recommends = []
+ recommend_shixuns = recommend_shixun(shixun)
+ recommend_shixuns.each do |rec|
+ recommends << {:name => rec.name, :myshixuns_count => rec.myshixuns_count, :trainee => rec.trainee, :image_url => url_to_avatar(rec), :identifier => rec.identifier}
+ end
+
+ return {:current_user => current_user, :shixun => shixun.try(:attributes), :fork_info => fork_info, :creator => creator, :star_info => star_info,
+ :myshixuns_count => shixun.myshixuns_count, :shixun_score => shixun.shixun_score, :shixun_level => shixun.shixun_level,
+ :fork_count => fork_count, :recommend_shixuns => recommends, :myshixun => myshixun.try(:attributes), :watched => watched}
+ end
+
+ # 实训讨论帖子
+ def shixun_discuss params, current_user
+ page = params[:page].to_i
+ offset = page * LIMIT
+ dis_id = params[:container_id] # 如:shixun_id
+ dis = Shixun.select([:id, :user_id]).find(dis_id)
+ dis_type = params[:container_type] # 如:"Shixun"
+ # 总数,分页使用
+ if current_user.admin?
+ disscuss_count = Discuss.where(dis_id: dis_id, dis_type: dis_type, root_id: nil).count
+ discusses = Discuss.where(dis_id: dis_id, dis_type: dis_type, root_id: nil)
+ .includes(:user, :praise_tread).limit(LIMIT).offset(offset)
+ else
+ disscusses = Discuss.where(
+ 'dis_id = :dis_id and dis_type = :dis_type and root_id is null and (hidden = :hidden or user_id = :user_id)',
+ dis_id: dis_id, dis_type: dis_type, hidden: false, user_id: current_user.id
+ )
+
+ disscuss_count = disscusses.count
+ discusses = disscusses.includes(:user, :praise_tread).limit(LIMIT).offset(offset)
+ end
+
+ base_data discusses, dis, current_user
+
+ { children_list: @children_list, disscuss_count: disscuss_count }
+ end
+
+ # 获取某条消息的具体位置(比如在分页中的某一页)
+ def anchor params, current_user
+ discuss_id = params[:discuss_id].to_i
+ dis_id = params[:container_id] # 如:shixun_id
+ dis = Shixun.select([:id, :user_id]).find(dis_id)
+ dis_type = params[:container_type] # 如:"Shixun"
+ # 注意:此处查询的条件必须和列表的条件一致,否则结果定位可能会有问题
+ position_discusses = Discuss.find_by_sql("SELECT @rowno:=@rowno+1 as rowno, r.id from discusses r,(select @rowno:=0) m where root_id is null and
+ dis_id=#{dis_id} and dis_type='#{dis_type}' order by created_at desc")
+ disscuss_count = position_discusses.size
+ # 标记是否找到评论
+ find_status = false
+ if disscuss_count > 0
+ position = position_discusses.select{|discuss| discuss.id == discuss_id}.first.try(:rowno)
+ page = position.to_i / LIMIT
+ offset = page * LIMIT
+ find_status = true if position
+ end
+ discusses = Discuss.limit(LIMIT).where(:dis_id => dis_id, :dis_type => dis_type, :root_id => nil).includes(:user, :praise_tread).offset(offset)
+
+ base_data discusses, dis, current_user
+ Myshixun.find(params[:myshixun_id]).update_attribute(:onclick_time, Time.now)
+ return {:children_list => @children_list, :disscuss_count => disscuss_count, :page => page.to_i, :find_status => find_status}
+ end
+
+ # 判断当前用户是否有新的评论消息
+ def new_message params, current_user
+ onclick_time = Myshixun.find(params[:myshixun_id]).try(:onclick_time)
+ dis_id = params[:container_id]
+ dis_type = params[:container_type]
+ # 获取当前用户发布的帖子,如果帖子有非自己的回复则通知我
+ ids = Discuss.where("user_id =? and dis_id =? and dis_type =? and root_id is null", current_user.id, dis_id, dis_type).pluck(:id)
+ user_discuss = Discuss.where("user_id !=? and created_at >?", current_user.id, onclick_time).where(:root_id => ids, :dis_id => dis_id, :dis_type => dis_type).first
+
+ return {:new_message => user_discuss}
+ end
+
+ # 公共数据结构
+ # 注意:shixun_id如果是非实训类型的,一定要主要其中的权限判断(目前只适用于实训)
+ def base_data discusses, dis, current_user
+ @children_list = []
+ # 目前只取十个,不多N+1问题不大
+ # 需要彻底解决则需要改数据路结构,比如Nested算法可以解决
+ if discusses.present?
+ discusses.each do |d|
+ # 总点赞数
+ praise_count = d.praise_tread.where(:praise_or_tread => 1).count
+ user_praise= d.praise_tread.select{|pt| pt.user_id == current_user.id}.length > 0 ? true : false
+ manager = current_user.manager_of_shixun?(dis, current_user)
+ game_url =
+ if manager
+ position = d.position.nil? ? 1 : d.position
+ challenge_id = dis.challenges.where(position: position).pluck(:id).first
+ game_identifier = Game.where(user_id: current_user,
+ challenge_id: challenge_id).pluck(:identifier).first
+ "/tasks/#{game_identifier}"
+ else
+ ""
+ end
+ p_content =
+ if d.hidden
+ manager ? d.content : "违规评论已被屏蔽!"
+ else
+ d.content
+ end
+ # 实训(TPM)的管理员可以看到隐藏的评论
+ parents = {:id => d.id, :content => p_content, :time => time_from_now(d.created_at), :position => d.position,
+ :reward => d.reward, :user => d.user, :shixun_id => dis.id, :hidden => d.hidden, game_url: game_url,
+ :manager => manager, :praise_count => praise_count, :user_praise => user_praise, :admin => current_user.admin?}
+
+ # 现在没有二级回复,所以查询的时候直接从root_id取
+ children =
+ if current_user.admin?
+ Discuss.where(root_id: d.id).includes(:user).reorder("created_at asc")
+ else
+ Discuss.where('root_id = :root_id and (hidden = :hidden or user_id = :user_id)',
+ root_id: d.id, hidden: false, user_id: current_user.id).includes(:user).reorder("created_at asc")
+ end
+
+ @children_list << parents.merge(
+ {:children => (children.map{|child|
+ [:content => (child.hidden ? (manager ? child.content : "违规评论已被屏蔽!") : child.content),
+ :time => time_from_now(child.created_at), :position => child.position ,
+ :reward => child.reward,:hidden => child.hidden, :user => d.user,
+ :can_delete => child.can_deleted?(current_user),
+ :id => child.id]}.flatten if children.present?)
+ })
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/users/apply_trail_service.rb b/app/services/users/apply_trail_service.rb
new file mode 100644
index 000000000..61563c301
--- /dev/null
+++ b/app/services/users/apply_trail_service.rb
@@ -0,0 +1,56 @@
+class Users::ApplyTrailService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :user, :remote_ip, :params
+
+ def initialize(user, params)
+ @user = user
+ @remote_ip = params.delete(:remote_ip)
+ @params = params
+ end
+
+ def call
+ Users::ApplyTrailForm.new(params.merge(user: user)).validate!
+
+ ActiveRecord::Base.transaction do
+ bind_user_phone! unless user.phone_binded?
+
+ apply = ApplyAction.find_or_initialize_by(user_id: user.id, container_type: 'TrialAuthorization', status: 0)
+ apply.assign_attributes(ip_addr: remote_ip, apply_reason: params[:reason]) if apply.new_record?
+
+ # 自动授权
+ if auto_authorization_school_student?
+ user.update!(certification: 1)
+
+ apply.status = 1
+ else
+ send_trial_apply_notify!
+ end
+ apply.save!
+ end
+
+ user
+ end
+
+ private
+
+ def bind_user_phone!
+ code = VerificationCode.where(phone: params[:phone], code: params[:code], code_type: 4).last
+
+ raise Error, '无效的验证码' if code.blank? || !code.effective?
+
+ user.update!(phone: params[:phone])
+ end
+
+ def auto_authorization_school_student?
+ user.user_extension&.student? && School.exists?(auto_users_trial: true, id: user.user_extension&.school_id)
+ end
+
+ def send_trial_apply_notify!
+ Educoder::Sms.notify_admin(send_type:'user_apply_auth')
+ rescue => ex
+ Rails.logger.error('发送通知管理员短信失败')
+ Rails.logger.error(ex.message)
+ ex.backtrace.each { |msg| Rails.logger.error(msg) }
+ end
+end
\ No newline at end of file
diff --git a/app/services/users/attendance_service.rb b/app/services/users/attendance_service.rb
new file mode 100644
index 000000000..a42e77a80
--- /dev/null
+++ b/app/services/users/attendance_service.rb
@@ -0,0 +1,27 @@
+class Users::AttendanceService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :user
+
+ def initialize(user)
+ @user = user
+ end
+
+ def call
+ nearly_attendance = Attendance.find_by(user_id: user.id)
+ raise Error, '您已签到过了' if nearly_attendance&.today?
+
+ attendance = nil
+ ActiveRecord::Base.transaction do
+ gold = nearly_attendance&.next_gold || 50
+
+ user.increment!(:grade, gold)
+
+ attendance = user.attendances.create!(score: gold)
+
+ Grade.create!(user_id: user.id, score: gold, container_id: user.id, container_type: 'Attendance')
+ end
+
+ attendance
+ end
+end
diff --git a/app/services/users/bind_email_service.rb b/app/services/users/bind_email_service.rb
new file mode 100644
index 000000000..02cf8b91a
--- /dev/null
+++ b/app/services/users/bind_email_service.rb
@@ -0,0 +1,28 @@
+class Users::BindEmailService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ Users::BindEmailForm.new(params).validate!
+
+ raise Error, '该邮箱已被绑定' if User.where.not(id: user.id).exists?(mail: params[:email])
+
+ code = VerificationCode.where(mail: params[:email], code: params[:code], code_type: 4).last
+ raise Error, '验证码无效' unless code&.effective?
+
+ ActiveRecord::Base.transaction do
+ if user.mail.blank?
+ RewardGradeService.call(user, container_id: user.id, container_type: 'Mail', score: 500)
+ end
+
+ user.mail = params[:email]
+ user.save!
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/users/bind_phone_service.rb b/app/services/users/bind_phone_service.rb
new file mode 100644
index 000000000..0ea39ae9b
--- /dev/null
+++ b/app/services/users/bind_phone_service.rb
@@ -0,0 +1,28 @@
+class Users::BindPhoneService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ Users::BindPhoneForm.new(params).validate!
+
+ raise Error, '该手机号已被绑定' if User.where.not(id: user.id).exists?(phone: params[:phone])
+
+ code = VerificationCode.where(phone: params[:phone], code: params[:code], code_type: 5).last
+ raise Error, '验证码无效' unless code&.effective?
+
+ ActiveRecord::Base.transaction do
+ if user.phone.blank?
+ RewardGradeService.call(user, container_id: user.id, container_type: 'Phone', score: 500)
+ end
+
+ user.phone = params[:phone]
+ user.save!
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/services/users/course_service.rb b/app/services/users/course_service.rb
new file mode 100644
index 000000000..6271db17b
--- /dev/null
+++ b/app/services/users/course_service.rb
@@ -0,0 +1,52 @@
+class Users::CourseService
+ include CustomSortable
+
+ sort_columns :updated_at, default_by: :updated_at, default_direction: :desc
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ courses = category_scope_courses.deleted(false)
+
+ courses = status_filter(courses)
+
+ custom_sort(courses, :updated_at, params[:sort_direction])
+ end
+
+ private
+
+ def category_scope_courses
+ case params[:category]
+ when 'study' then
+ user.as_student_courses
+ when 'manage' then
+ user.manage_courses
+ else
+ ids = user.as_student_courses.pluck(:id) + user.manage_courses.pluck(:id)
+ Course.where(id: ids)
+ end
+ end
+
+ def status_filter(relations)
+ # 只有自己查看才有过滤
+ return relations unless observed_logged_user?
+
+ case params[:status]
+ when 'processing' then
+ relations.ended(false)
+ when 'end' then
+ relations.ended(true)
+ else
+ relations
+ end
+ end
+
+ def observed_logged_user?
+ User.current.id == user.id
+ end
+end
diff --git a/app/services/users/project_service.rb b/app/services/users/project_service.rb
new file mode 100644
index 000000000..7296c0a58
--- /dev/null
+++ b/app/services/users/project_service.rb
@@ -0,0 +1,51 @@
+class Users::ProjectService
+ include CustomSortable
+
+ sort_columns :updated_on, default_by: :updated_on, default_direction: :desc
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ projects = Project.joins(members: :member_roles).where(members: { user_id: user.id })
+
+ keyword = params[:keyword].to_s.strip
+ projects = projects.where('name LIKE ?', "%#{keyword}%") if keyword.present?
+
+ projects = projects.where.not(status: 9) # without archived status
+
+ projects = category_filter(projects)
+ projects = status_filter(projects)
+
+ custom_sort(projects, :updated_on, params[:sort_direction])
+ end
+
+ private
+
+ def category_filter(relations)
+ roles = case params[:category]
+ when 'study' then [4, 5]
+ when 'manage' then 3
+ else [3, 4, 5]
+ end
+ relations.where(member_roles: { role_id: roles })
+ end
+
+ def status_filter(relations)
+ return relations unless self_or_admin?
+
+ case params[:status]
+ when 'publicly' then relations.where(is_public: true)
+ when 'personal' then relations.where(is_public: false)
+ else relations
+ end
+ end
+
+ def self_or_admin?
+ User.current.id == user.id || User.current.admin?
+ end
+end
\ No newline at end of file
diff --git a/app/services/users/question_bank_service.rb b/app/services/users/question_bank_service.rb
new file mode 100644
index 000000000..b17073a4f
--- /dev/null
+++ b/app/services/users/question_bank_service.rb
@@ -0,0 +1,99 @@
+class Users::QuestionBankService
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ relations = class_name.classify.constantize.all
+
+ relations = category_filter(relations)
+ relations = type_filter(relations) if params[:type].present?
+
+ relations = relations.where(course_list_id: params[:course_list_id]) if params[:course_list_id].present?
+
+ custom_sort(relations, params[:sort_by], params[:sort_direction])
+ end
+
+ def course_lists
+ relation_name = class_name.underscore.pluralize.to_sym
+ course_lists = CourseList.joins(relation_name).where.not(relation_name => { id: nil })
+
+ category_condition =
+ case params[:category]
+ when 'common' then { homework_type: 1 }
+ when 'group' then { homework_type: 3 }
+ when 'exercise' then { container_type: 'Exercise' }
+ when 'poll' then { container_type: 'Poll' }
+ when 'gtask', 'gtopic' then {}
+ else raise ArgumentError
+ end
+ course_lists = course_lists.where(relation_name => category_condition) if category_condition.present?
+
+ type_condition =
+ case params[:type]
+ when 'personal' then { user_id: user.id }
+ when 'publicly' then { is_public: true }
+ else {}
+ end
+ course_lists = course_lists.where(relation_name => type_condition) if type_condition.present?
+
+ course_lists.distinct.select(:id, :name)
+ end
+
+ private
+
+ def class_name
+ @_class_name ||= begin
+ case params[:category]
+ when 'common', 'group' then 'HomeworkBank'
+ when 'exercise', 'poll' then 'ExerciseBank'
+ when 'gtask' then 'GtaskBank'
+ when 'gtopic' then 'GtopicBank'
+ else raise ArgumentError
+ end
+ end
+ end
+
+ def category_filter(relations)
+ case params[:category]
+ when 'common' then
+ relations.where(homework_type: 1)
+ when 'group' then
+ relations.where(homework_type: 3)
+ when 'exercise' then
+ relations.where(container_type: 'Exercise')
+ when 'poll' then
+ relations.where(container_type: 'Poll')
+ when 'gtask', 'gtopic' then
+ relations.all
+ else
+ raise ArgumentError
+ end
+ end
+
+ def type_filter(relations)
+ case params[:type]
+ when 'personal' then relations.where(user_id: user.id)
+ when 'publicly' then relations.where(is_public: true)
+ else relations
+ end
+ end
+
+ def custom_sort(relations, sort_by, sort_direction)
+ case sort_by
+ when 'updated_at' then
+ relations.order(updated_at: sort_direction)
+ when 'name' then
+ relations.order("CONVERT(name USING gbk) COLLATE gbk_chinese_ci #{sort_direction}")
+ when 'contributor' then
+ order_sql = "CONVERT (users.lastname USING gbk) COLLATE gbk_chinese_ci #{sort_direction},"\
+ " CONVERT (users.firstname USING gbk) COLLATE gbk_chinese_ci #{sort_direction}"
+ relations.joins(:user).where(users: { status: 1 }).order(order_sql)
+ else
+ relations
+ end
+ end
+end
diff --git a/app/services/users/shixun_service.rb b/app/services/users/shixun_service.rb
new file mode 100644
index 000000000..b5e5586f1
--- /dev/null
+++ b/app/services/users/shixun_service.rb
@@ -0,0 +1,97 @@
+class Users::ShixunService
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ shixuns = category_scope_shixuns
+
+ shixuns = user_policy_filter(shixuns)
+
+ custom_order(shixuns, params[:sort_by], params[:sort_direction])
+ end
+
+ private
+
+ def category_scope_shixuns
+ case params[:category]
+ when 'study' then
+ user.study_shixuns
+ when 'manage' then
+ user.shixuns
+ else
+ ids = user.study_shixuns.pluck(:id) + user.shixuns.pluck(:id)
+ Shixun.where(id: ids)
+ end
+ end
+
+ def status_filter(relations)
+ case params[:category]
+ when 'study' then
+ study_shixun_status_filter(relations)
+ when 'manage' then
+ manage_shixun_status_filter(relations)
+ else
+ relations
+ end
+ end
+
+ def user_policy_filter(relations)
+ # 只有自己或者管理员才有过滤筛选及查看全部状态下实训功能
+ if self_or_admin?
+ relations = relations.where.not(status: -1)
+ status_filter(relations)
+ else
+ relations.where(status: [2, 3], hidden: false)
+ end
+ end
+
+ def self_or_admin?
+ User.current.id == user.id || User.current.admin?
+ end
+
+ def study_shixun_status_filter(relations)
+ status = case params[:status]
+ when 'passed' then 1
+ when 'processing' then 0
+ end
+ relations.where(myshixuns: { status: status }) if status
+ relations
+ end
+
+ def manage_shixun_status_filter(relations)
+ status = case params[:status]
+ when 'editing' then 0
+ when 'applying' then 1
+ when 'published' then 2
+ when 'closed' then 3
+ end
+ relations = relations.where(status: status) if status
+ relations
+ end
+
+ def custom_order(relations, sort_by, sort_direction)
+ sort_by = sort_by&.downcase
+ sort_direction = sort_direction&.downcase
+
+ if sort_direction.blank? || !%w(desc asc).include?(sort_direction)
+ sort_direction = 'desc'
+ end
+
+ if sort_by == 'language'
+ return relations.left_joins(:tag_repertoires).order("tag_repertoires.name #{sort_direction}")
+ end
+
+ case params[:category]
+ when 'study' then
+ relations.order("myshixuns.updated_at #{sort_direction}")
+ when 'manage' then
+ relations.order("shixuns.updated_at #{sort_direction}")
+ else
+ relations.order("shixuns.created_at #{sort_direction}")
+ end
+ end
+end
diff --git a/app/services/users/subject_service.rb b/app/services/users/subject_service.rb
new file mode 100644
index 000000000..e0d8377c2
--- /dev/null
+++ b/app/services/users/subject_service.rb
@@ -0,0 +1,85 @@
+class Users::SubjectService
+ include CustomSortable
+
+ sort_columns :updated_at, default_by: :updated_at, default_direction: :desc
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ subjects = category_scope_subjects
+ subjects = user_policy_filter(subjects)
+
+ custom_sort(subjects, :updated_at, params[:sort_direction])
+ end
+
+ private
+
+ def category_scope_subjects
+ case params[:category]
+ when 'study' then
+ Subject.joins(stage_shixuns: { shixun: :myshixuns }).where(myshixuns: { user_id: user.id })
+ when 'manage' then
+ Subject.joins(:subject_members).where(subject_members: { user_id: user.id })
+ else
+ study_subject_ids = StageShixun.where(shixun_id: user.myshixuns.pluck(:shixun_id)).pluck(:subject_id)
+ manage_subject_ids = user.subject_members.pluck(:subject_id)
+ Subject.where(id: study_subject_ids + manage_subject_ids)
+ end
+ end
+
+ def user_policy_filter(relations)
+ # 只有自己或者管理员才有过滤筛选及查看全部状态下实训功能
+ if self_or_admin?
+ status_filter(relations)
+ else
+ relations.where(status: 2, hidden: false)
+ end
+ end
+
+ def status_filter(relations)
+ return relations unless self_or_admin?
+
+ case params[:category]
+ when 'study' then
+ study_subject_status_filter(relations)
+ when 'manage' then
+ manage_subject_status_filter(relations)
+ else
+ relations
+ end
+ end
+
+ def study_subject_status_filter(relations)
+ subjects = Subject.joins(shixuns: :myshixuns)
+ .where(myshixuns: { user_id: user.id })
+ .select('subjects.id, (COUNT(IF(myshixuns.status=1, 1, 0)) = subjects.stages_count) finished')
+ .group('subjects.id')
+ subject_ids =
+ case params[:status]
+ when 'unfinished' then subjects.having('finished = 0').map(&:id)
+ when 'finished' then subjects.having('finished = 1').map(&:id)
+ end
+
+ relations.where(id: subject_ids) if subject_ids.present?
+ relations
+ end
+
+ def manage_subject_status_filter(relations)
+ status = case params[:status]
+ when 'editing' then 0
+ when 'applying' then 1
+ when 'published' then 2
+ end
+ relations.where(status: status) if status
+ relations
+ end
+
+ def self_or_admin?
+ User.current.id == user.id || User.current.admin?
+ end
+end
diff --git a/app/services/users/update_account_service.rb b/app/services/users/update_account_service.rb
new file mode 100644
index 000000000..6a8588dee
--- /dev/null
+++ b/app/services/users/update_account_service.rb
@@ -0,0 +1,61 @@
+class Users::UpdateAccountService < ApplicationService
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ form = Users::UpdateAccountForm.new(params.merge(user: user))
+ form.validate!
+
+ first_full_reward = false
+ ActiveRecord::Base.transaction do
+ first_full_reward = user.nickname.blank?
+
+ extension = user.user_extension || user.build_user_extension
+
+ user.assign_attributes(user_attributes)
+ extension.assign_attributes(user_extension_attributes)
+
+ # 未认证下才能修改姓名
+ if !user.authentication? && user.process_real_name_apply.blank?
+ user.lastname = params[:name]
+ user.firstname = ''
+ extension.gender = params[:gender]
+ end
+
+ if extension.student?
+ extension.student_id = params[:student_id]
+ extension.technical_title = nil
+ else
+ extension.student_id = nil
+ extension.technical_title = params[:technical_title]
+ end
+
+ # 表示资料完整
+ user.profile_completed = true
+
+ extension.save!
+ user.save!
+ end
+
+ if first_full_reward
+ RewardGradeService.call(user, container_id: user.id, container_type: 'Account', score: 500)
+ end
+
+ user
+ end
+
+ private
+
+ def user_attributes
+ params.slice(*%i[nickname show_realname])
+ end
+
+ def user_extension_attributes
+ params.slice(*%i[location location_city identity student_id technical_title school_id department_id])
+ end
+end
\ No newline at end of file
diff --git a/app/services/users/update_password_service.rb b/app/services/users/update_password_service.rb
new file mode 100644
index 000000000..0df32eb76
--- /dev/null
+++ b/app/services/users/update_password_service.rb
@@ -0,0 +1,32 @@
+class Users::UpdatePasswordService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ Users::UpdatePasswordForm.new(params).validate!
+
+ raise Error, '旧密码不匹配' unless user.check_password?(params[:old_password])
+
+ ActiveRecord::Base.transaction do
+ user.update!(password: params[:password])
+
+ if user.gid.present?
+ # 同步修改gitlab密码
+ begin
+ Gitlab.client.edit_user(user.gid, password: params[:password])
+ rescue Exception => ex
+ Rails.logger.error(ex.message)
+ raise Error, '修改失败'
+ end
+ end
+ end
+
+ user
+ end
+end
\ No newline at end of file
diff --git a/app/templates/exercise_export/blank_exercise.html.erb b/app/templates/exercise_export/blank_exercise.html.erb
new file mode 100644
index 000000000..61bde0d27
--- /dev/null
+++ b/app/templates/exercise_export/blank_exercise.html.erb
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+<%= @exercise.try(:exercise_name) %>
+ <%= @exercise.try(:exercise_name) %>
+
+
+
+ 关卡
+ 任务名称
+ 评测次数
+ 完成时间
+ 耗时
+ 经验值
+ 得分/满分
+
+
+ <% if @games.size > 0 %>
+ <% @games.each_with_index do |game, index| %>
+ <% user_score = q.exercise_shixun_answers.where(exercise_shixun_challenge_id:game.challenge.id,user_id: @ex_user_user.id) %>
+ <% game_score = q.exercise_shixun_challenges.where(challenge_id:game.challenge.id) %>
+
+
+ <% end %>
+ <% else %>
+ <% q.exercise_shixun_challenges.each_with_index do |game, index| %>
+ <% game_score = q.exercise_shixun_challenges.where(challenge_id:game.challenge.id) %>
+ <%= index + 1 %>
+
+ <%= game.challenge.subject %>
+
+ <%= game.evaluate_count %>
+ <%= game.end_time.present? ? game.end_time.strftime("%Y-%m-%d %H:%M") : "--" %>
+ <%= ApplicationController.helpers.time_consuming game %>
+ <%= game.final_score %> / <%= game.challenge.all_score %>
+ <%= user_score.present? ? user_score.first.score : 0.0 %> / <%= game_score.present? ? game_score.first.question_score : 0.0 %>
+
+
+ <% end %>
+ <% end %>
+
+ <%= index + 1 %>
+
+ <%= game.challenge.subject %>
+
+ 0
+ --
+ --
+ 0.0 / <%= game.challenge.all_score %>
+ 0.0 / <%= game_score.present? ? game_score.first.question_score : 0.0 %>
+
+
+
+ <% end %>
+ <% if game.try(:lastest_code).present? && game.challenge.st == 0 %>
+ <% con_rows = content_line(game.lastest_code) %>
+ 评测次数
+ 评测信息
+
+
+ <% outputs = game.outputs.group("query_index") %>
+ <% outputs.reverse.try(:each) do |output| %>
+
+
+ <% end %>
+
+ <%= "第#{output.query_index}次" %>
+ <%= output_detail(game, output) %>
+
+
+
+ 总评
+ 总经验值
+ 作业成绩
+ 耗时
+ 评测次数
+
+
+
+
+
+ <%= @work.overall_appraisal %>
+ <%= @work.myshixun&.total_score %> / <%= @shixun.all_score %>
+ <%= ApplicationController.helpers.number_with_precision(@work.work_score, precision: 1) %> / 100
+ <%= @work.myshixun_consume %>
+ <%= @user_evaluate_count.to_i %>
+
+
+
+ 关卡
+ 任务名称
+ 开启时间
+ 评测次数
+ 完成时间
+ 耗时
+ 经验值
+
+
+ <% @games.each_with_index do |game, index| %>
+
+
+ <% end %>
+
+ <%= index + 1 %>
+
+ <%= game.challenge.subject %>
+ <% if ((Time.now > @homework.end_time) && game.end_time.blank?) || (game.end_time.present? && game.end_time > @homework.end_time) %>
+ 延时
+ <% end %>
+
+ <%= myshixun_open_time(game) %>
+ <%= game.evaluate_count %>
+ <%= finished_time game.end_time %>
+ <%= ApplicationController.helpers.time_consuming game %>
+ <%= game.final_score %> / <%= game.challenge.all_score %>
+
+
+
+
+
+
+
+ <% end %>
+ <% if game.try(:lastest_code).present? && game.challenge.st == 0 %>
+ 评测次数
+ 评测信息
+
+
+ <% outputs = game.outputs.group("query_index") %>
+ <% max_query = outputs.map(&:query_index).max %>
+ <% outputs.reverse.try(:each) do |output| %>
+
+
+ <% end %>
+
+ <%= max_query == output.query_index ? "最后一次" : "第#{output.query_index}次" %>
+ <%= output_detail(game, output) %>
+ <%= pluralize(edu_setting.errors.count, "error") %> prohibited this edu_setting from being saved:
+
+
+ <% edu_setting.errors.full_messages.each do |message| %>
+
+ Editing Edu Setting
+
+<%= render 'form', edu_setting: @edu_setting %>
+
+<%= link_to 'Show', @edu_setting %> |
+<%= link_to 'Back', edu_settings_path %>
diff --git a/app/views/edu_settings/index.html.erb b/app/views/edu_settings/index.html.erb
new file mode 100644
index 000000000..d49977254
--- /dev/null
+++ b/app/views/edu_settings/index.html.erb
@@ -0,0 +1,30 @@
+EduCoder公共配置
+
+
+
+
+
+
+
+
+
+ <% @edu_settings.each do |edu_setting| %>
+ 变量名
+ 变量值
+
+
+
+ <% end %>
+
+<%= edu_setting.name %>
+ <%= edu_setting.value %>
+ <%= link_to 'Show', edu_setting %>
+ <%= link_to 'Edit', edit_edu_setting_path(edu_setting) %>
+ <%= link_to 'Destroy', edu_setting, method: :delete, data: { confirm: 'Are you sure?' } %>
+
+
+<%= link_to 'New Edu Setting', new_edu_setting_path %>
diff --git a/app/views/edu_settings/index.json.jbuilder b/app/views/edu_settings/index.json.jbuilder
new file mode 100644
index 000000000..bcc5948be
--- /dev/null
+++ b/app/views/edu_settings/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @edu_settings, partial: 'edu_settings/edu_setting', as: :edu_setting
diff --git a/app/views/edu_settings/new.html.erb b/app/views/edu_settings/new.html.erb
new file mode 100644
index 000000000..8a70e3a63
--- /dev/null
+++ b/app/views/edu_settings/new.html.erb
@@ -0,0 +1,5 @@
+New Edu Setting
+
+<%= render 'form', edu_setting: @edu_setting %>
+
+<%= link_to 'Back', edu_settings_path %>
diff --git a/app/views/edu_settings/show.html.erb b/app/views/edu_settings/show.html.erb
new file mode 100644
index 000000000..9d027373f
--- /dev/null
+++ b/app/views/edu_settings/show.html.erb
@@ -0,0 +1,14 @@
+<%= @exercise.try(:exercise_name) %>
+
+
+
+ 关卡
+ 任务名称
+ 评测次数
+ 完成时间
+ 耗时
+ 经验值
+ 得分/满分
+
+
+ <% if @games.size > 0 %>
+ <% @games.each_with_index do |game, index| %>
+ <% user_score = q.exercise_shixun_answers.where(exercise_shixun_challenge_id:game.challenge.id,user_id:31798) %>
+ <% game_score = q.exercise_shixun_challenges.where(challenge_id:game.challenge.id) %>
+
+
+ <% end %>
+ <% else %>
+ <% q.exercise_shixun_challenges.each_with_index do |game, index| %>
+ <% game_score = q.exercise_shixun_challenges.where(challenge_id:game.challenge.id) %>
+ <%= index + 1 %>
+
+ <%= game.challenge.subject %>
+
+ <%= game.evaluate_count %>
+ <%= game.end_time.present? ? game.end_time.strftime("%Y-%m-%d %H:%M") : "--" %>
+ <%= ApplicationController.helpers.time_consuming game %>
+ <%= game.final_score %> / <%= game.challenge.all_score %>
+ <%= user_score.present? ? user_score.first.score : 0.0 %> / <%= game_score.first.question_score %>
+
+
+ <% end %>
+ <% end %>
+
+
+ <%= index + 1 %>
+
+ <%= game.challenge.subject %>
+
+ 0
+ --
+ --
+ 0.0 / <%= game.challenge.all_score %>
+ 0.0 / <%= game_score.first.question_score %>
+
+
+
+ <% end %>
+ <% if game.try(:lastest_code).present? && game.challenge.st == 0 %>
+ 评测次数
+ 评测信息
+
+
+ <% outputs = game.outputs.group("query_index") %>
+ <% outputs.reverse.try(:each) do |output| %>
+
+
+ <% end %>
+
+ <%= "第#{output.query_index}次" %>
+ <%= output_detail(game, output) %>
+ <%= pluralize(tem_test.errors.count, "error") %> prohibited this tem_test from being saved:
+
+
+ <% tem_test.errors.full_messages.each do |message| %>
+
+ Editing Tem Test
+
+<%= render 'form', tem_test: @tem_test %>
+
+<%= link_to 'Show', @tem_test %> |
+<%= link_to 'Back', tem_tests_path %>
diff --git a/app/views/tem_tests/index.html.erb b/app/views/tem_tests/index.html.erb
new file mode 100644
index 000000000..96a3d843f
--- /dev/null
+++ b/app/views/tem_tests/index.html.erb
@@ -0,0 +1,29 @@
+Tem Tests
+
+
+
+
+
+
+
+
+
+
+ <% @tem_tests.each do |tem_test| %>
+ Name
+ Email
+
+
+
+ <% end %>
+
+<%= tem_test.name %>
+ <%= tem_test.email %>
+ <%= link_to 'Show', tem_test %>
+ <%= link_to 'Edit', edit_tem_test_path(tem_test) %>
+ <%= link_to 'Destroy', tem_test, method: :delete, data: { confirm: 'Are you sure?' } %>
+
+
+<%= link_to 'New Tem Test', new_tem_test_path %>
diff --git a/app/views/tem_tests/index.json.jbuilder b/app/views/tem_tests/index.json.jbuilder
new file mode 100644
index 000000000..d77e48320
--- /dev/null
+++ b/app/views/tem_tests/index.json.jbuilder
@@ -0,0 +1 @@
+json.array! @tem_tests, partial: 'tem_tests/tem_test', as: :tem_test
diff --git a/app/views/tem_tests/new.html.erb b/app/views/tem_tests/new.html.erb
new file mode 100644
index 000000000..38b7a49c6
--- /dev/null
+++ b/app/views/tem_tests/new.html.erb
@@ -0,0 +1,5 @@
+New Tem Test
+
+<%= render 'form', tem_test: @tem_test %>
+
+<%= link_to 'Back', tem_tests_path %>
diff --git a/app/views/tem_tests/show.html.erb b/app/views/tem_tests/show.html.erb
new file mode 100644
index 000000000..9d6eeaf3e
--- /dev/null
+++ b/app/views/tem_tests/show.html.erb
@@ -0,0 +1,14 @@
+
+ 要解决问题或了解您的帐户详情,您可以访问 帮助中心。
+
+
+ 要解决问题或了解您的帐户详情,您可以访问 帮助中心。
+
+
原因:%{reason}"
+ AnonymousAppeal_end: "开启了匿评申诉:%{name}"
+ HomeworkPublish_end: "发布了作业:%{name}"
+ NearlyEnd_end: "作业的提交截止时间快到啦:%{name}"
+ AppealNearlyEnd_end: "作品的匿评申诉时间快到啦:%{name}"
+ EvaluationNearlyEnd_end: "作业的匿评截止时间快到啦:%{name}"
+ StudentWork:
+ true_end: "提交了作品:%s"
+ false_end: "重新提交了作品,建议您重新评阅:%s"
+ StudentWorksScore:
+ 1_end: "评阅了你的作品:%s"
+ 2_end: "评阅了你的作品:%s"
+ 3_end: "有人匿评了你的作品:%s"
+ ChallengeWorkScore_end: "调整了你的作品分数:%s"
+ StudentWorksScoresAppeal:
+ UserAppealResult:
+ 1_end: "同意了你提交的匿评申诉申请:%s"
+ 2_end: "拒绝了你提交的匿评申诉:%s"
+ AppealResult:
+ 1_end: "同意了他人对你的匿评申诉申请:%s"
+ 2_end: "拒绝了他人对你的匿评申诉:%s"
+ StudentWork:
+ Apply_end: "发起了匿评申诉申请:%s"
+ HomeworkCommon_end: "有人对你的匿评发起了申诉:%s"
+ Department_end: "你选填的二级单位:%s(%s)因不符合规范,已被系统删除.请重新选择"
diff --git a/config/locales/users/zh-CN.yml b/config/locales/users/zh-CN.yml
new file mode 100644
index 000000000..5c4ad76a7
--- /dev/null
+++ b/config/locales/users/zh-CN.yml
@@ -0,0 +1,12 @@
+'zh-CN':
+ user:
+ identity:
+ teacher: 教师
+ student: 学生
+ professional: 专业人士
+ developer: 从业者
+ enterprise: 组织
+ "0": 教师
+ "1": 学生
+ "2": 专业人士
+ "3": 专业人士
\ No newline at end of file
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
new file mode 100644
index 000000000..99d5ac843
--- /dev/null
+++ b/config/locales/zh-CN.yml
@@ -0,0 +1,4 @@
+'zh-CN':
+ error:
+ record_not_found: 您访问的页面不存在或已被删除
+ forbidden: 您没有权限进行该操作
\ No newline at end of file
diff --git a/config/puma.rb b/config/puma.rb
new file mode 100644
index 000000000..e31c00feb
--- /dev/null
+++ b/config/puma.rb
@@ -0,0 +1,34 @@
+# Puma can serve each request in a thread from an internal thread pool.
+# 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.
+#
+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.
+#
+port ENV.fetch("PORT") { 3000 }
+
+# Specifies the `environment` that Puma will run in.
+#
+environment ENV.fetch("RAILS_ENV") { "development" }
+
+# Specifies the number of `workers` to boot in clustered mode.
+# Workers are forked webserver processes. If using threads and workers together
+# the concurrency of the application would be max `threads` * `workers`.
+# Workers do not work on JRuby or Windows (both of which do not support
+# processes).
+#
+# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
+
+# 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.
+#
+# preload_app!
+
+# Allow puma to be restarted by `rails restart` command.
+plugin :tmp_restart
diff --git a/config/redis.yml b/config/redis.yml
new file mode 100644
index 000000000..71e61cfd9
--- /dev/null
+++ b/config/redis.yml
@@ -0,0 +1,12 @@
+defaults: &defaults
+ url: <%= ENV["REDIS_URL"] || "redis://localhost:6379/0" %>
+
+development:
+ <<: *defaults
+
+test:
+ <<: *defaults
+
+production:
+ <<: *defaults
+ url: redis://10.9.72.102:6379/0/job
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
new file mode 100644
index 000000000..e7fac7331
--- /dev/null
+++ b/config/routes.rb
@@ -0,0 +1,656 @@
+Rails.application.routes.draw do
+
+ resources :edu_settings
+ scope '/api' do
+ get 'home/index'
+ get 'home/search'
+
+ post 'praise_tread/like', to: 'praise_tread#like'
+ delete 'praise_tread/unlike', to: 'praise_tread#unlike'
+
+ put 'commons/hidden', to: 'commons#hidden'
+ put 'commons/unhidden', to: 'commons#unhidden'
+ delete 'commons/delete', to: 'commons#delete'
+
+ resources :memos
+ resources :tem_tests
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+ #
+ #
+ resources :accounts do
+
+ collection do
+ post :login
+ post :register
+ post :reset_password
+ get :logout
+ get :get_verification_code
+ get :valid_email_and_phone
+ end
+
+ end
+
+ resources :users do
+ member do
+ get :homepage_info
+ end
+
+ scope module: :users do
+ resources :courses, only: [:index]
+ resources :shixuns, only: [:index]
+ resources :projects, only: [:index]
+ resources :subjects, only: [:index]
+ resources :question_banks, only: [:index]
+ resource :experience_records, only: [:show]
+ resource :grade_records, only: [:show]
+ resource :watch, only: [:create, :destroy]
+ end
+
+
+ collection do
+ post :following
+ post :unfollow
+ get :get_user_info
+ get :attachment_show
+ get :get_navigation_info
+ post :reply_message
+ get :search_user_projects
+ post :brief_introduction
+ post :attendance
+
+ resource :trial_apply, only: [:create]
+ resources :projects, only: [] do
+ get :search, on: :collection
+ end
+
+ resources :tidings, only: [:index]
+
+ scope module: :users do
+ resources :accounts, only: [:show, :update] do
+ resource :phone_bind, only: [:create]
+ resource :email_bind, only: [:create]
+ resource :password, only: [:update]
+ resource :avatar, only: [:update]
+ end
+ end
+ end
+ end
+
+ resources :myshixuns, param: :identifier, shallow: true do
+ member do
+ post :repository
+ post :commits
+ post :file_content
+ post :update_file
+ get :reset_my_game
+ post :html_content
+ get :open_webssh
+ get :challenges
+ end
+ collection do
+ get :sigle_mul_test
+ match :training_task_status, :via => [:get, :post]
+ match :code_runinng_message, :via => [:get, :post]
+ end
+ resources :games
+ end
+
+ resources :games, path: :tasks, param: :identifier do
+ member do
+ get :star
+ get :git_entries
+ get :answer
+ get :answer_grade
+ get :rep_content
+ get :reset_original_code
+ get :reset_passed_code
+ post :file_update
+ post :choose_build
+ get :game_build
+ get :game_status
+ post :plus_or_cancel_praise
+ get :cost_time
+ get :system_update
+ get :sync_modify_time
+ get :picture_display
+ get :sync_codes
+ get :close_webssh
+ get :get_answer_info
+ get :unlock_answer
+
+ end
+ collection do
+ get :challenges
+ end
+ end
+
+ resources :shixuns, param: :identifier do
+ collection do
+ get :menus
+ get :get_recommend_shixuns
+ get :departments
+ get :get_mirror_script
+ post :apply_shixun_mirror
+ get :download_file
+ end
+
+ member do
+ post :copy
+ get :propaedeutics
+ get :show_right
+ get :operation
+ get :ranking_list
+ get :discusses
+ get :tasks
+ get :collaborators
+ get :settings
+ get :get_script_contents
+ get :get_custom_script
+ post :repository
+ post :commits
+ post :file_content
+ post :update_file
+ post :close
+ get :fork_list
+ post :update_propaedeutics
+ get :add_collaborators
+ post :shixun_members_added
+ match :change_manager, :via => [:get, :post]
+ get :search_user_courses
+ post :send_to_course
+ delete :collaborators_delete
+ get :cancel_publish
+ get :publish
+ get :shixun_exec
+ end
+
+ resources :challenges do
+ member do
+ get 'index_up'
+ get 'index_down'
+ post 'create_choose_question'
+ get 'show_choose_question'
+ match 'choose_type_show', :via => [:get, :post]
+ match 'edit_choose_question', :via => [:get, :post]
+ match 'update_choose_question', :via => [:get, :post]
+ delete 'destroy_challenge_choose'
+ post :crud_answer
+ get :answer
+
+ end
+ end
+
+ resources :repositories do
+ collection do
+ post :add_project
+ post :fork_project
+
+ post :file_tree # 目录树
+ post :update_file # 文件更新
+ post :file_content # 文件内容
+
+ post :commits # 提交记录
+ end
+ end
+ end
+
+ resources :discusses do
+ collection do
+ get :new_message
+ end
+
+ member do
+ post :reply
+ post :hidden
+ post :reward_code
+ post :plus
+ end
+ end
+
+ resources :subjects, path: :paths do
+ member do
+ get 'choose_subject_shixun'
+ get 'publish'
+ get 'cancel_publish'
+ get 'cancel_has_publish'
+ get 'statistics'
+ get 'shixun_report'
+ get 'school_report'
+ post 'update_attr'
+ post :search_members
+ post 'add_subject_members'
+ delete 'delete_member'
+ get :choose_course
+ post 'send_to_course'
+ delete :delete_member
+ post :up_member_position
+ post :down_member_position
+ end
+
+ collection do
+ get 'create_subject'
+ get 'new_subject'
+ post 'append_to_stage'
+ get 'search'
+ end
+ end
+
+ resources :stages do
+ member do
+ get 'down_position'
+ get 'up_position'
+ end
+ end
+
+ resources :files, only: [:index, :show, :update] do
+ collection do
+ delete :bulk_delete
+ put :bulk_move
+ post :bulk_send
+ put :bulk_public
+ get :public_with_course_and_project
+ get :mine_with_course_and_project
+ post :import
+ post :upload
+ end
+ member do
+ get :histories
+ end
+ end
+
+ resources :courses do
+ member do
+ get 'settings', :action => 'settings', :as => 'settings'
+ post 'set_invite_code_halt'
+ post 'set_public_or_private'
+ post 'search_teacher_candidate'
+ post 'add_teacher'
+ post 'create_graduation_group'
+ post 'join_graduation_group'
+ post 'set_course_group'
+ post 'change_course_admin'
+ post 'change_course_teacher'
+ post 'delete_course_teacher'
+ post 'teacher_application_review'
+ post 'transfer_to_course_group'
+ post 'delete_from_course'
+ post 'add_students_by_search'
+ post 'create_group_by_importing_file'
+ post 'duplicate_course'
+ post 'visits_plus_one'
+ get 'get_historical_courses'
+ get 'get_historical_course_students'
+ get 'course_group_list'
+ get 'add_teacher_popup'
+ get 'teachers'
+ get 'graduation_group_list'
+ get 'top_banner'
+ get 'left_banner'
+ get 'students'
+ get 'all_course_groups'
+ get 'search_users'
+ get 'base_info'
+ get 'attahcment_category_list'
+ get 'export_member_scores_excel' #导出课堂信息
+ post 'switch_to_teacher'
+ post 'switch_to_assistant'
+ post 'switch_to_student'
+ post 'exit_course'
+ end
+
+ collection do
+ post 'apply_to_join_course'
+ post 'search_course_list'
+ get 'board_list'
+ get 'mine'
+ end
+
+ resources :polls, only:[:index,:new,:create] do
+ collection do
+ post :publish # 立即发布
+ post :end_poll # 立即截止
+ post :destroys # 多个删除
+ post :set_public # 设置公开
+ post :join_poll_banks # 加入习题集
+ get :my_polls #我的问卷题库
+ get :public_polls # 公共问卷题库
+ get :publish_modal # 立即发布弹窗内容
+ get :end_poll_modal # 立即截止弹窗内容
+ end
+ end
+
+ resources :homework_commons, shallow: true do
+
+ member do
+ get :group_list
+ post :homework_code_repeat
+ get :code_review_results
+ get :code_review_detail
+ post :update_explanation
+ get :show_comment
+ get :settings
+ post :update_settings
+ match 'works_list', :via => [:get, :post]
+ # post :works_list
+ get :reference_answer
+ get :publish_groups
+ get :end_groups
+ post :alter_name
+ end
+
+ collection do
+ post 'create_shixun_homework'
+ match 'shixuns', via: [:get, :post]
+ match 'subjects', via: [:get, :post]
+ post 'create_subject_homework'
+ post 'publish_homework'
+ post 'end_homework'
+ post 'set_public'
+ post 'move_to_category'
+ get 'choose_category'
+ post 'multi_destroy'
+ post 'add_to_homework_bank'
+ end
+
+ resources :student_works do
+ member do
+ get :shixun_work
+ get :shixun_work_report
+ post :adjust_review_score
+ get :commit_des
+ post :update_des
+ post :adjust_score
+ post :add_score
+ post :add_score_reply
+ delete :destroy_score
+ delete :destroy_score_reply
+ get :comment_list
+ get :supply_attachments
+ post :revise_attachment
+ delete :destroy_score
+ post :appeal_anonymous_score
+ post :deal_appeal_score
+ post :cancel_appeal
+ get :export_shixun_work_report
+ end
+
+ collection do
+ get :search_member_list
+ get :check_project
+ get :cancel_relate_project
+ post :relate_project
+ end
+ end
+ end
+
+
+ resources :boards, shallow: true do
+ resources :messages do
+ collection do
+ delete :bulk_delete
+ put :bulk_move
+ post :bulk_send
+ put :bulk_public
+ end
+
+ member do
+ get :reply_list
+ put :sticky_top
+ post :reply
+ end
+ end
+ member do
+ post 'move_category'
+ end
+ end
+
+ resources :exercises ,only:[:index,:new,:create] do
+ collection do
+ get :my_exercises #我的试卷题库
+ get :public_exercises # 公共试卷题库
+ get :publish_modal # 立即发布弹窗内容
+ get :end_modal # 立即截止弹窗内容
+ post :destroys
+ post :set_public # 设置公开
+ post :join_exercise_banks # 加入习题集
+ post :publish # 立即发布
+ post :end_exercise # 立即截止
+
+ end
+ end
+
+ resources :course_groups, shallow: true do
+ member do
+ post 'rename_group'
+ post 'move_category'
+ end
+
+ collection do
+ end
+ end
+
+ resources :graduation_topics do
+ member do
+ post :refuse_student_topic
+ post :accept_student_topic
+ post :student_select_topic
+ post :student_cancel_topic
+ get :show_detail
+ get :show_comment
+ end
+ collection do
+ delete :destroys
+ post :set_public
+ get :export
+ post :add_to_bank
+ end
+ end
+
+ resources :graduation_tasks, shallow: true do
+ resources :graduation_works do
+ collection do
+ post 'search_member_list'
+ get 'check_project'
+ post 'relate_project'
+ get 'cancel_relate_project'
+ post 'revise_attachment'
+ end
+
+ member do
+ get 'comment_list'
+ post 'add_score'
+ post 'adjust_score'
+ delete 'delete_score'
+ get 'supply_attachments'
+ post 'revise_attachment'
+ post :assign_teacher
+ end
+ end
+ member do
+ get 'settings'
+ post 'update_settings'
+ get 'tasks_list'
+ get :show_comment
+ end
+
+ collection do
+ post 'set_public'
+ delete 'multi_destroy'
+ post 'publish_task'
+ post 'end_task'
+ post 'add_to_bank'
+ end
+ end
+ end
+
+ resources :polls,except:[:index,:new,:create] do
+ member do
+ get :poll_setting
+ post :commit_setting
+ get :start_answer
+ post :commit_poll
+ get :commit_result
+ get :poll_lists # 问卷的答题列表
+ post :cancel_publish #撤销发布
+ get :cancel_publish_modal #撤销发布的弹窗
+ get :common_header
+ end
+ resources :poll_questions,only:[:new,:create]
+ end
+
+ resources :poll_questions,except:[:new,:create,:index] do
+ member do
+ post :delete_answer
+ post :up_down
+ post :commit_answer
+ end
+ resource :poll_votes,only:[:create,:destroy]
+ end
+
+ resources :question_banks do
+ collection do
+ get :bank_list
+ post :save_banks
+ end
+ end
+
+
+ resources :exercises do
+ member do
+ get :choose_shixun
+ get :commit_shixun
+ get :exercise_setting
+ post :commit_setting
+ get :start_answer
+ post :commit_exercise
+ get :redo_modal #打回重做弹窗内容
+ post :redo_exercise
+ get :review_exercise
+ get :exercise_lists
+ # get :blank_exercise #摒弃,仅作为测试html页面才会使用
+ get :export_exercise
+ get :common_header
+ get :exercise_result
+ post :cancel_exercise
+ get :begin_commit #提交前的弹窗
+ end
+ resources :exercise_questions,only:[:new,:create,:index]
+ end
+
+ resources :exercise_questions,except:[:new,:create,:index] do
+ member do
+ post :up_down
+ post :delete_answer
+ post :adjust_score
+ end
+ resource :exercise_answers,only:[:create,:destroy]
+ end
+
+
+ resources :course_modules, shallow: true do
+ member do
+ get 'sticky_module'
+ get 'hidden_module'
+ post 'rename_module'
+ post 'add_second_category'
+ end
+ collection do
+ post 'unhidden_modules'
+ end
+ end
+
+ resources :course_second_categories, shallow: true do
+ member do
+ post 'rename_category'
+ post 'move_category'
+ end
+ collection do
+
+ end
+ end
+ resources :attachments
+
+ resources :schools do
+ member do
+ end
+ collection do
+ get "school_list"
+ end
+
+ scope module: :ecs do
+ get :detail, to: 'homes#index'
+
+ resources :ec_majors, only: [:index]
+ resources :ec_major_schools, only: [:index, :create, :destroy]
+ end
+ end
+
+ # 为避免url过长以及层级过深,路由定义和controller继承都做了处理
+ scope module: :ecs do
+ resources :ec_major_schools, only: [] do
+ resources :major_managers, only: [:create, :destroy]
+ resources :ec_years, only: [:index, :create, :destroy]
+ end
+
+ resources :ec_years, only: [] do
+ resource :ec_training_objectives, only: [:show, :create]
+ resources :ec_graduation_requirements, only: [:index, :create]
+ resource :requirement_support_objectives, only: [:show, :create, :destroy]
+ resource :subitem_support_standards, only: [:show, :create, :destroy]
+ resource :students, only: [:show, :destroy] do
+ post :import, on: :collection
+ end
+
+ resources :ec_courses, only: [:index, :create, :destroy] do
+ post :link_course, on: :member
+
+ collection do
+ post :import
+ get :search
+ end
+ end
+
+ resource :graduation_course_supports, only: [:show, :create]
+ resource :reach_evaluation, only: [:show, :create]
+ resource :reach_criteria, only: [:create]
+ end
+
+ resources :ec_courses, only: [] do
+ resource :evaluation, only: [:show, :create]
+ resources :course_managers, only: [:create, :destroy]
+ resources :course_targets, only: [:index, :create] do
+ get :with_achievement_methods, on: :collection
+
+ resource :course_achievement_methods, only: [:create]
+ end
+ resources :course_evaluations, only: [:index, :create, :update, :destroy] do
+ member do
+ get :average_score_import_template
+ get :detail_score_import_template
+ get :import_student_achievement
+ end
+ get :slimmer, on: :collection
+ end
+ resource :score_levels, only: [:show, :create]
+ end
+ end
+
+ resource :zip, only: [] do
+ collection do
+ get :shixun_report
+ get :export_exercises
+ end
+ end
+ end
+
+ #git 认证回调
+ match 'gitauth/*url', to: 'gits#auth', via: :all
+
+ get 'oauth/get_code', to: 'oauth#get_code'
+ get 'oauth/get_token_callback', to: 'oauth#get_token_callback'
+
+ root 'main#index'
+
+ ## react用
+ get '*path', to: 'main#index', constraints: ReactConstraint.new
+end
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
new file mode 100644
index 000000000..a2b3fc0be
--- /dev/null
+++ b/config/sidekiq.yml
@@ -0,0 +1,6 @@
+:concurrency: <%= ENV["sidekiq_threads"] || 20 %>
+:pidfile: tmp/pids/sidekiq.pid
+:logfile: log/sidekiq.log
+:queues:
+ - [default, 3]
+ - [notify, 100]
\ No newline at end of file
diff --git a/config/spring.rb b/config/spring.rb
new file mode 100644
index 000000000..3d9795158
--- /dev/null
+++ b/config/spring.rb
@@ -0,0 +1,6 @@
+%w[
+ .ruby-version
+ .rbenv-vars
+ tmp/restart.txt
+ tmp/caching-dev.txt
+].each { |path| Spring.watch(path) }
diff --git a/config/storage.yml b/config/storage.yml
new file mode 100644
index 000000000..a6cba4441
--- /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/migrate/20180920063955_create_graduation_topics.rb b/db/migrate/20180920063955_create_graduation_topics.rb
new file mode 100644
index 000000000..5e7a4640e
--- /dev/null
+++ b/db/migrate/20180920063955_create_graduation_topics.rb
@@ -0,0 +1,26 @@
+class CreateGraduationTopics < ActiveRecord::Migration[5.2]
+ def change
+ create_table :graduation_topics do |t|
+ t.references :user
+ t.references :course
+ t.integer :tea_id
+ t.string :name
+ t.text :description
+ t.integer :status, :default => 0
+ t.integer :topic_type
+ t.integer :source
+ t.integer :property
+ t.integer :property_two
+ t.string :source_unit
+ t.integer :repeat
+ t.string :province
+ t.string :city
+ t.boolean :is_public, :default => 0
+
+ t.timestamps
+ end
+ add_index :graduation_topics, :user_id
+ add_index :graduation_topics, :course_id
+ add_index :graduation_topics, :tea_id
+ end
+end
diff --git a/db/migrate/20181024015607_add_column_to_poll.rb b/db/migrate/20181024015607_add_column_to_poll.rb
new file mode 100644
index 000000000..65ebe98d3
--- /dev/null
+++ b/db/migrate/20181024015607_add_column_to_poll.rb
@@ -0,0 +1,6 @@
+class AddColumnToPoll < ActiveRecord::Migration[5.2]
+ # 问卷是否实名
+ def change
+ add_column :polls, :un_anonymous, :boolean, :default => 0
+ end
+end
diff --git a/db/migrate/20181105031300_add_repo_name_to_shixuns.rb b/db/migrate/20181105031300_add_repo_name_to_shixuns.rb
new file mode 100644
index 000000000..3210184ad
--- /dev/null
+++ b/db/migrate/20181105031300_add_repo_name_to_shixuns.rb
@@ -0,0 +1,5 @@
+class AddRepoNameToShixuns < ActiveRecord::Migration[5.2]
+ def change
+ add_column :shixuns, :repo_name, :string
+ end
+end
diff --git a/db/migrate/20181105061745_add_repo_name_to_myshixuns.rb b/db/migrate/20181105061745_add_repo_name_to_myshixuns.rb
new file mode 100644
index 000000000..70ca92abb
--- /dev/null
+++ b/db/migrate/20181105061745_add_repo_name_to_myshixuns.rb
@@ -0,0 +1,5 @@
+class AddRepoNameToMyshixuns < ActiveRecord::Migration[5.2]
+ def change
+ add_column :myshixuns, :repo_name, :string
+ end
+end
diff --git a/db/migrate/20181115071148_create_relationships.rb b/db/migrate/20181115071148_create_relationships.rb
new file mode 100644
index 000000000..4437e9ea5
--- /dev/null
+++ b/db/migrate/20181115071148_create_relationships.rb
@@ -0,0 +1,13 @@
+class CreateRelationships < ActiveRecord::Migration[5.2]
+ def change
+ create_table :relationships do |t|
+ t.integer :follower_id
+ t.integer :followed_id
+
+ t.timestamps
+ end
+ add_index :relationships, :followed_id
+ add_index :relationships, :follower_id
+ add_index :relationships, [:follower_id, :followed_id], unique: true
+ end
+end
diff --git a/db/migrate/20181118075620_sync_gitlab_admin.rb b/db/migrate/20181118075620_sync_gitlab_admin.rb
new file mode 100644
index 000000000..17cb3bd18
--- /dev/null
+++ b/db/migrate/20181118075620_sync_gitlab_admin.rb
@@ -0,0 +1,6 @@
+class SyncGitlabAdmin < ActiveRecord::Migration[5.2]
+ def change
+ user = User.find_by_login("forge01")
+ user.update_column(:mail, 'admin@example.com')
+ end
+end
diff --git a/db/migrate/20181207064509_add_cloud_url_to_attachments.rb b/db/migrate/20181207064509_add_cloud_url_to_attachments.rb
new file mode 100644
index 000000000..6dd3ffb6d
--- /dev/null
+++ b/db/migrate/20181207064509_add_cloud_url_to_attachments.rb
@@ -0,0 +1,5 @@
+class AddCloudUrlToAttachments < ActiveRecord::Migration[5.2]
+ def change
+ add_column :attachments, :cloud_url, :string, default: ''
+ end
+end
diff --git a/db/migrate/20181213024859_add_averge_star_to_shixuns.rb b/db/migrate/20181213024859_add_averge_star_to_shixuns.rb
new file mode 100644
index 000000000..c801c8675
--- /dev/null
+++ b/db/migrate/20181213024859_add_averge_star_to_shixuns.rb
@@ -0,0 +1,5 @@
+class AddAvergeStarToShixuns < ActiveRecord::Migration[5.2]
+ def change
+ add_column :shixuns, :averge_star, :float, :default => 0
+ end
+end
diff --git a/db/migrate/20181213025239_sync_averger_star_to_shixun.rb b/db/migrate/20181213025239_sync_averger_star_to_shixun.rb
new file mode 100644
index 000000000..e117475a4
--- /dev/null
+++ b/db/migrate/20181213025239_sync_averger_star_to_shixun.rb
@@ -0,0 +1,12 @@
+class SyncAvergerStarToShixun < ActiveRecord::Migration[5.2]
+ def change
+ Shixun.find_each do |shixun|
+ averge_star = Game.find_by_sql("select ifnull(sum(g.star),0)/ifnull(count(*),1) as averge_star from (games g left join
+ (myshixuns m join shixuns s on s.id = m.shixun_id) on m.id = g.myshixun_id)
+ where star != 0 and s.id = #{shixun.id}").first.try(:averge_star)
+ averge_star = averge_star || 5
+ puts(averge_star)
+ shixun.update_column(:averge_star, averge_star.round(1))
+ end
+ end
+end
diff --git a/db/migrate/20181228095820_create_shixun_infos.rb b/db/migrate/20181228095820_create_shixun_infos.rb
new file mode 100644
index 000000000..f5c72d5a3
--- /dev/null
+++ b/db/migrate/20181228095820_create_shixun_infos.rb
@@ -0,0 +1,12 @@
+class CreateShixunInfos < ActiveRecord::Migration[5.2]
+ def change
+ create_table :shixun_infos do |t|
+ t.longtext :propaedeutics
+ t.longtext :description
+ t.longtext :evaluate_script
+ t.integer :shixun_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20181228115324_add_index_to_users.rb b/db/migrate/20181228115324_add_index_to_users.rb
new file mode 100644
index 000000000..ccd8f771f
--- /dev/null
+++ b/db/migrate/20181228115324_add_index_to_users.rb
@@ -0,0 +1,5 @@
+class AddIndexToUsers < ActiveRecord::Migration[5.2]
+ def change
+ add_index :users, :experience
+ end
+end
diff --git a/db/migrate/20190108074759_modify_status_and_visits_for_subjects.rb b/db/migrate/20190108074759_modify_status_and_visits_for_subjects.rb
new file mode 100644
index 000000000..08a5d0e3a
--- /dev/null
+++ b/db/migrate/20190108074759_modify_status_and_visits_for_subjects.rb
@@ -0,0 +1,6 @@
+class ModifyStatusAndVisitsForSubjects < ActiveRecord::Migration[5.2]
+ def change
+ change_column :subjects, :status, :integer, :default => 0
+ change_column :subjects, :visits, :integer, :default => 1
+ end
+end
diff --git a/db/migrate/20190109005614_add_opening_time_for_shixuns.rb b/db/migrate/20190109005614_add_opening_time_for_shixuns.rb
new file mode 100644
index 000000000..d02cd6113
--- /dev/null
+++ b/db/migrate/20190109005614_add_opening_time_for_shixuns.rb
@@ -0,0 +1,5 @@
+class AddOpeningTimeForShixuns < ActiveRecord::Migration[5.2]
+ def change
+ add_column :shixuns, :opening_time, :string
+ end
+end
diff --git a/db/migrate/20190109013210_modify_opening_time_for_shixuns.rb b/db/migrate/20190109013210_modify_opening_time_for_shixuns.rb
new file mode 100644
index 000000000..b3360c424
--- /dev/null
+++ b/db/migrate/20190109013210_modify_opening_time_for_shixuns.rb
@@ -0,0 +1,5 @@
+class ModifyOpeningTimeForShixuns < ActiveRecord::Migration[5.2]
+ def change
+ change_column :shixuns, :opening_time, :datetime
+ end
+end
diff --git a/db/migrate/20190111063956_add_averge_star_default_for_shixuns.rb b/db/migrate/20190111063956_add_averge_star_default_for_shixuns.rb
new file mode 100644
index 000000000..fb0b4d3aa
--- /dev/null
+++ b/db/migrate/20190111063956_add_averge_star_default_for_shixuns.rb
@@ -0,0 +1,5 @@
+class AddAvergeStarDefaultForShixuns < ActiveRecord::Migration[5.2]
+ def change
+ change_column :shixuns, :averge_star, :float, :default => 5
+ end
+end
diff --git a/db/migrate/20190120063702_sync_reponame.rb b/db/migrate/20190120063702_sync_reponame.rb
new file mode 100644
index 000000000..c40eba131
--- /dev/null
+++ b/db/migrate/20190120063702_sync_reponame.rb
@@ -0,0 +1,16 @@
+class SyncReponame < ActiveRecord::Migration[5.2]
+ def change
+ g = Gitlab.client
+ myshixuns = Myshixun.where("repo_name is null")
+ myshixuns.find_each do |myshixun|
+ begin
+ puts myshixun.id
+ repo_name = g.project(myshixun.gpid).path_with_namespace
+ puts repo_name
+ myshixun.update_column(:repo_name, repo_name)
+ rescue Exception => e
+ Rails.logger.error("e.message")
+ end
+ end
+ end
+end
diff --git a/db/migrate/20190220014052_add_shixuns_count_to_stage.rb b/db/migrate/20190220014052_add_shixuns_count_to_stage.rb
new file mode 100644
index 000000000..caf459a8e
--- /dev/null
+++ b/db/migrate/20190220014052_add_shixuns_count_to_stage.rb
@@ -0,0 +1,5 @@
+class AddShixunsCountToStage < ActiveRecord::Migration[5.2]
+ def change
+ add_column :stages, :shixuns_count, :integer, default: 0
+ end
+end
diff --git a/db/migrate/20190220061013_add_shixuns_count_to_subject.rb b/db/migrate/20190220061013_add_shixuns_count_to_subject.rb
new file mode 100644
index 000000000..3f803d2f1
--- /dev/null
+++ b/db/migrate/20190220061013_add_shixuns_count_to_subject.rb
@@ -0,0 +1,5 @@
+class AddShixunsCountToSubject < ActiveRecord::Migration[5.2]
+ def change
+ add_column :subjects, :shixuns_count, :integer, default: 0
+ end
+end
diff --git a/db/migrate/20190220074947_sync_cache_count_for_subjects.rb b/db/migrate/20190220074947_sync_cache_count_for_subjects.rb
new file mode 100644
index 000000000..ce2b22d62
--- /dev/null
+++ b/db/migrate/20190220074947_sync_cache_count_for_subjects.rb
@@ -0,0 +1,8 @@
+class SyncCacheCountForSubjects < ActiveRecord::Migration[5.2]
+ def change
+ Subject.find_each do |s|
+ Subject.reset_counters s.id, :shixuns
+ Subject.reset_counters s.id, :stages
+ end
+ end
+end
diff --git a/db/migrate/20190221003048_add_users_count_to_shixun.rb b/db/migrate/20190221003048_add_users_count_to_shixun.rb
new file mode 100644
index 000000000..dffca5be9
--- /dev/null
+++ b/db/migrate/20190221003048_add_users_count_to_shixun.rb
@@ -0,0 +1,5 @@
+class AddUsersCountToShixun < ActiveRecord::Migration[5.2]
+ def change
+ add_column :shixuns, :users_count, :integer, default: 0
+ end
+end
diff --git a/db/migrate/20190221003137_sync_users_count_for_shixun.rb b/db/migrate/20190221003137_sync_users_count_for_shixun.rb
new file mode 100644
index 000000000..155ddda58
--- /dev/null
+++ b/db/migrate/20190221003137_sync_users_count_for_shixun.rb
@@ -0,0 +1,7 @@
+class SyncUsersCountForShixun < ActiveRecord::Migration[5.2]
+ def change
+ Shixun.find_each do |s|
+ Shixun.reset_counters s.id, :users_count
+ end
+ end
+end
diff --git a/db/migrate/20190228030508_delete_none_myshixun_games.rb b/db/migrate/20190228030508_delete_none_myshixun_games.rb
new file mode 100644
index 000000000..89130d3fa
--- /dev/null
+++ b/db/migrate/20190228030508_delete_none_myshixun_games.rb
@@ -0,0 +1,6 @@
+class DeleteNoneMyshixunGames < ActiveRecord::Migration[5.2]
+ def change
+ games = Game.where.not(myshixun_id: Myshixun.where(nil).pluck(:id))
+ games.destroy_all
+ end
+end
diff --git a/db/migrate/20190304082236_create_challenge_answers.rb b/db/migrate/20190304082236_create_challenge_answers.rb
new file mode 100644
index 000000000..630c92661
--- /dev/null
+++ b/db/migrate/20190304082236_create_challenge_answers.rb
@@ -0,0 +1,13 @@
+class CreateChallengeAnswers < ActiveRecord::Migration[5.2]
+ def change
+ create_table :challenge_answers do |t|
+ t.string :name
+ t.longtext :contents
+ t.integer :score
+ t.integer :level
+ t.references :challenge, index: true
+ t.timestamps
+ end
+
+ end
+end
diff --git a/db/migrate/20190304082811_create_course_members.rb b/db/migrate/20190304082811_create_course_members.rb
new file mode 100644
index 000000000..fd54891fd
--- /dev/null
+++ b/db/migrate/20190304082811_create_course_members.rb
@@ -0,0 +1,19 @@
+class CreateCourseMembers < ActiveRecord::Migration[5.2]
+ def change
+ create_table :course_members do |t|
+ t.integer :course_id
+ t.integer :user_id
+ t.integer :course_group_id, default: 0
+ t.integer :graduation_group_id, default: 0
+ t.integer :role, default: 0
+ t.boolean :is_active, default: 1
+
+ t.timestamps
+ end
+
+ add_index :course_members, :course_id
+ add_index :course_members, :user_id
+ add_index :course_members, :course_group_id
+ add_index :course_members, :graduation_group_id
+ end
+end
diff --git a/db/migrate/20190305015556_modify_answer_open_for_games.rb b/db/migrate/20190305015556_modify_answer_open_for_games.rb
new file mode 100644
index 000000000..7ae83ac9e
--- /dev/null
+++ b/db/migrate/20190305015556_modify_answer_open_for_games.rb
@@ -0,0 +1,5 @@
+class ModifyAnswerOpenForGames < ActiveRecord::Migration[5.2]
+ def change
+ change_column :games, :answer_open, :integer, :default => 0
+ end
+end
diff --git a/db/migrate/20190305074102_add_checkout_answer_score_for_games.rb b/db/migrate/20190305074102_add_checkout_answer_score_for_games.rb
new file mode 100644
index 000000000..9c026fc3f
--- /dev/null
+++ b/db/migrate/20190305074102_add_checkout_answer_score_for_games.rb
@@ -0,0 +1,5 @@
+class AddCheckoutAnswerScoreForGames < ActiveRecord::Migration[5.2]
+ def change
+ add_column :games, :answer_deduction, :integer, :default => 0
+ end
+end
diff --git a/db/migrate/20190305080703_add_course_members_count_to_course.rb b/db/migrate/20190305080703_add_course_members_count_to_course.rb
new file mode 100644
index 000000000..d38aaca5b
--- /dev/null
+++ b/db/migrate/20190305080703_add_course_members_count_to_course.rb
@@ -0,0 +1,9 @@
+class AddCourseMembersCountToCourse < ActiveRecord::Migration[5.2]
+ def change
+ add_column :courses, :course_members_count, :integer, default: 0
+
+ Course.find_each do |c|
+ Course.reset_counters c.id, :course_members
+ end
+ end
+end
diff --git a/db/migrate/20190306023331_add_score_for_test_sets.rb b/db/migrate/20190306023331_add_score_for_test_sets.rb
new file mode 100644
index 000000000..08b67ad85
--- /dev/null
+++ b/db/migrate/20190306023331_add_score_for_test_sets.rb
@@ -0,0 +1,6 @@
+class AddScoreForTestSets < ActiveRecord::Migration[5.2]
+ def change
+ add_column :test_sets, :score, :integer
+ add_column :challenges, :test_set_score, :boolean, :default => false
+ end
+end
diff --git a/db/migrate/20190306064421_add_answer_for_challenge_answer.rb b/db/migrate/20190306064421_add_answer_for_challenge_answer.rb
new file mode 100644
index 000000000..32a6a6aa2
--- /dev/null
+++ b/db/migrate/20190306064421_add_answer_for_challenge_answer.rb
@@ -0,0 +1,10 @@
+class AddAnswerForChallengeAnswer < ActiveRecord::Migration[5.2]
+ def change
+ challenges = Challenge.where("answer is not null")
+ challenges.find_each do |c|
+ puts "####################{c.id}"
+ ChallengeAnswer.create(name: "解题代码", contents: "#{c.answer}", level: 1, score: 100, challenge_id: c.id)
+ end
+
+ end
+end
diff --git a/db/migrate/20190307063217_add_pod_life_to_shixuns.rb b/db/migrate/20190307063217_add_pod_life_to_shixuns.rb
new file mode 100644
index 000000000..e596367f2
--- /dev/null
+++ b/db/migrate/20190307063217_add_pod_life_to_shixuns.rb
@@ -0,0 +1,5 @@
+class AddPodLifeToShixuns < ActiveRecord::Migration[5.2]
+ def change
+ add_column :shixuns, :pod_life, :integer, :default => 0
+ end
+end
diff --git a/db/migrate/20190309085449_migrate_course_members.rb b/db/migrate/20190309085449_migrate_course_members.rb
new file mode 100644
index 000000000..ef8741bfb
--- /dev/null
+++ b/db/migrate/20190309085449_migrate_course_members.rb
@@ -0,0 +1,40 @@
+class MigrateCourseMembers < ActiveRecord::Migration[5.2]
+ def change
+ add_column :course_groups, :position, :integer, default: 0
+
+ Course.find_each do |course|
+ position = 1
+ course.course_groups.reorder("CONVERT(course_groups.name USING gbk) COLLATE gbk_chinese_ci ASC").find_each do |group|
+ group.update_attribute(:position, position)
+ position += 1
+ end
+ end
+
+
+ add_column :course_groups, :course_members_count, :integer, default: 0
+
+ CourseGroup.find_each do |g|
+ CourseGroup.reset_counters g.id, :course_members
+ end
+
+ ActiveRecord::Base.transaction do
+ begin
+ Member.where("course_id != -1").find_each do |member|
+ if member.course && member.user
+ puts(member.course_id)
+ member.member_roles.each do |role|
+ course_member_role = role.role_id == 3 ? 1 : (role.role_id == 9 ? 2 : (role.role_id == 7 ? 3 : 4))
+ member_group_id = role.role_id == 10 ? member.course_group_id : 0
+ CourseMember.create!(course_id: member.course_id, user_id: member.user_id, course_group_id: member_group_id.to_i,
+ graduation_group_id: member.graduation_group_id.to_i, role: course_member_role, is_active: role.is_current)
+ end
+ end
+ end
+ rescue Exception => e
+ uid_logger_error(e.message)
+ tip_exception("migrate_course_members 迁移失败!")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+end
diff --git a/db/migrate/20190309091056_add_course_member_id.rb b/db/migrate/20190309091056_add_course_member_id.rb
new file mode 100644
index 000000000..86993d725
--- /dev/null
+++ b/db/migrate/20190309091056_add_course_member_id.rb
@@ -0,0 +1,21 @@
+class AddCourseMemberId < ActiveRecord::Migration[5.2]
+ def change
+ add_column :student_graduation_topics, :course_member_id, :integer
+ add_index :student_graduation_topics, :course_member_id
+
+ StudentGraduationTopic.find_each do |student|
+ course_member = CourseMember.where(course_id: student.course_id, user_id: student.user_id,
+ role: 4).first
+ student.update_column("course_member_id", course_member.try(:id)) if course_member.present?
+ end
+
+ add_column :teacher_course_groups, :course_member_id, :integer
+ add_index :teacher_course_groups, :course_member_id
+
+ TeacherCourseGroup.find_each do |teacher|
+ course_member = CourseMember.where(course_id: teacher.course_id, user_id: teacher.user_id,
+ role: [1, 2, 3]).first
+ teacher.update_column("course_member_id", course_member.try(:id)) if course_member.present?
+ end
+ end
+end
diff --git a/db/migrate/20190311064138_add_course_groups_count_to_course.rb b/db/migrate/20190311064138_add_course_groups_count_to_course.rb
new file mode 100644
index 000000000..ecd11e7cb
--- /dev/null
+++ b/db/migrate/20190311064138_add_course_groups_count_to_course.rb
@@ -0,0 +1,9 @@
+class AddCourseGroupsCountToCourse < ActiveRecord::Migration[5.2]
+ def change
+ add_column :courses, :course_groups_count, :integer, default: 0
+
+ Course.find_each do |c|
+ Course.reset_counters c.id, :course_groups
+ end
+ end
+end
diff --git a/db/migrate/20190311080729_add_position_to_course_groups.rb b/db/migrate/20190311080729_add_position_to_course_groups.rb
new file mode 100644
index 000000000..4a59a7d97
--- /dev/null
+++ b/db/migrate/20190311080729_add_position_to_course_groups.rb
@@ -0,0 +1,5 @@
+class AddPositionToCourseGroups < ActiveRecord::Migration[5.2]
+ def change
+
+ end
+end
diff --git a/db/migrate/20190311090818_create_course_second_categories.rb b/db/migrate/20190311090818_create_course_second_categories.rb
new file mode 100644
index 000000000..51850c2fd
--- /dev/null
+++ b/db/migrate/20190311090818_create_course_second_categories.rb
@@ -0,0 +1,12 @@
+class CreateCourseSecondCategories < ActiveRecord::Migration[5.2]
+ def change
+ create_table :course_second_categories do |t|
+ t.references :course
+ t.string :category_type
+ t.string :name
+ t.integer :position
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20190312024018_migrate_course_second_category.rb b/db/migrate/20190312024018_migrate_course_second_category.rb
new file mode 100644
index 000000000..382c6fd6f
--- /dev/null
+++ b/db/migrate/20190312024018_migrate_course_second_category.rb
@@ -0,0 +1,19 @@
+class MigrateCourseSecondCategory < ActiveRecord::Migration[5.2]
+ def change
+ add_column :homework_commons, :course_second_category_id, :integer, default: 0
+ add_index :homework_commons, :course_second_category_id
+ add_column :course_second_categories, :course_module_id, :integer
+ add_index :course_second_categories, :course_module_id
+
+ Course.find_each do |course|
+ homework_position = 1
+ course_module = course.course_modules.where(module_type: "shixun_homework").first
+ course.course_homework_categories.reorder("CONVERT(name USING gbk) COLLATE gbk_chinese_ci ASC").find_each do |category|
+ new_category = CourseSecondCategory.create!(course_id: course.id, name: category.name, category_type: "shixun_homework",
+ position: homework_position, course_module_id: course_module.id)
+ course.homework_commons.where(course_homework_category_id: category.id).update_all(course_second_category_id: new_category.id)
+ homework_position += 1
+ end
+ end
+ end
+end
diff --git a/db/migrate/20190312032623_add_course_second_category_to_attachment.rb b/db/migrate/20190312032623_add_course_second_category_to_attachment.rb
new file mode 100644
index 000000000..972422e43
--- /dev/null
+++ b/db/migrate/20190312032623_add_course_second_category_to_attachment.rb
@@ -0,0 +1,6 @@
+class AddCourseSecondCategoryToAttachment < ActiveRecord::Migration[5.2]
+ def change
+ add_column :attachments, :course_second_category_id, :integer, default: 0
+ add_index :attachments, :course_second_category_id
+ end
+end
diff --git a/db/migrate/20190321081720_add_praises_count_to_messages.rb b/db/migrate/20190321081720_add_praises_count_to_messages.rb
new file mode 100644
index 000000000..14f9260cb
--- /dev/null
+++ b/db/migrate/20190321081720_add_praises_count_to_messages.rb
@@ -0,0 +1,11 @@
+class AddPraisesCountToMessages < ActiveRecord::Migration[5.2]
+ def change
+ add_column :messages, :praises_count, :integer, :default => 0
+
+ Message.find_each do |msg|
+ total_count = msg.praise_treads.liker.count
+ puts "====> set message's praises_count to #{total_count}"
+ msg.update_column(:praises_count, total_count)
+ end
+ end
+end
diff --git a/db/migrate/20190321091429_change_praise_or_tread_default_in_praise_treads.rb b/db/migrate/20190321091429_change_praise_or_tread_default_in_praise_treads.rb
new file mode 100644
index 000000000..15dd3f6e9
--- /dev/null
+++ b/db/migrate/20190321091429_change_praise_or_tread_default_in_praise_treads.rb
@@ -0,0 +1,5 @@
+class ChangePraiseOrTreadDefaultInPraiseTreads < ActiveRecord::Migration[5.2]
+ def change
+ change_column_default :praise_treads, :praise_or_tread, from: nil, to: 1
+ end
+end
diff --git a/db/migrate/20190322065716_create_message_details.rb b/db/migrate/20190322065716_create_message_details.rb
new file mode 100644
index 000000000..5fd1ba7d8
--- /dev/null
+++ b/db/migrate/20190322065716_create_message_details.rb
@@ -0,0 +1,11 @@
+class CreateMessageDetails < ActiveRecord::Migration[5.2]
+ def change
+ create_table :message_details do |t|
+ t.longtext :content
+ t.integer :message_id
+
+ t.timestamps
+ end
+ add_index :message_details, :message_id
+ end
+end
diff --git a/db/migrate/20190322070515_move_content_of_messages_to_message_details.rb b/db/migrate/20190322070515_move_content_of_messages_to_message_details.rb
new file mode 100644
index 000000000..be747c898
--- /dev/null
+++ b/db/migrate/20190322070515_move_content_of_messages_to_message_details.rb
@@ -0,0 +1,10 @@
+class MoveContentOfMessagesToMessageDetails < ActiveRecord::Migration[5.2]
+ def change
+ Message.find_each do |msg|
+ unless msg.message_detail.present?
+ message_detail = MessageDetail.create(message: msg, content: msg.content)
+ puts "-------> create MessageDetail success: id is #{message_detail.id}"
+ end
+ end
+ end
+end
diff --git a/db/migrate/20190322091313_remove_content_from_messages.rb b/db/migrate/20190322091313_remove_content_from_messages.rb
new file mode 100644
index 000000000..ee6830e53
--- /dev/null
+++ b/db/migrate/20190322091313_remove_content_from_messages.rb
@@ -0,0 +1,5 @@
+class RemoveContentFromMessages < ActiveRecord::Migration[5.2]
+ def change
+ remove_column :messages, :content, :longtext
+ end
+end
diff --git a/db/migrate/20190323005303_modify_graduation_topics.rb b/db/migrate/20190323005303_modify_graduation_topics.rb
new file mode 100644
index 000000000..18998fdcd
--- /dev/null
+++ b/db/migrate/20190323005303_modify_graduation_topics.rb
@@ -0,0 +1,8 @@
+class ModifyGraduationTopics < ActiveRecord::Migration[5.2]
+ def change
+ rename_column :graduation_topics, :source, :topic_source
+ rename_column :graduation_topics, :property, :topic_property_first
+ rename_column :graduation_topics, :property_two, :topic_property_second
+ rename_column :graduation_topics, :repeat, :topic_repeat
+ end
+end
diff --git a/db/migrate/20190325022227_add_is_public_to_graduation_task.rb b/db/migrate/20190325022227_add_is_public_to_graduation_task.rb
new file mode 100644
index 000000000..159aa90e9
--- /dev/null
+++ b/db/migrate/20190325022227_add_is_public_to_graduation_task.rb
@@ -0,0 +1,8 @@
+class AddIsPublicToGraduationTask < ActiveRecord::Migration[5.2]
+ def change
+ add_column :graduation_tasks, :is_public, :boolean, default: false
+ add_column :graduation_tasks, :late_time, :datetime
+
+ GraduationTask.where(status: 3).update_all(status: 4)
+ end
+end
diff --git a/db/migrate/20190325023830_change_digest_to_string_in_attachments.rb b/db/migrate/20190325023830_change_digest_to_string_in_attachments.rb
new file mode 100644
index 000000000..3b59e56e8
--- /dev/null
+++ b/db/migrate/20190325023830_change_digest_to_string_in_attachments.rb
@@ -0,0 +1,5 @@
+class ChangeDigestToStringInAttachments < ActiveRecord::Migration[5.2]
+ def change
+ change_column :attachments, :digest, :string, :limit => 60
+ end
+end
diff --git a/db/migrate/20190326015900_add_hidden_for_journal_for_messages.rb b/db/migrate/20190326015900_add_hidden_for_journal_for_messages.rb
new file mode 100644
index 000000000..2ff25360d
--- /dev/null
+++ b/db/migrate/20190326015900_add_hidden_for_journal_for_messages.rb
@@ -0,0 +1,5 @@
+class AddHiddenForJournalForMessages < ActiveRecord::Migration[5.2]
+ def change
+ add_column :journals_for_messages, :hidden, :boolean, :default => false
+ end
+end
diff --git a/db/migrate/20190326095920_add_is_hidden_to_messages.rb b/db/migrate/20190326095920_add_is_hidden_to_messages.rb
new file mode 100644
index 000000000..ee97a3f6c
--- /dev/null
+++ b/db/migrate/20190326095920_add_is_hidden_to_messages.rb
@@ -0,0 +1,6 @@
+class AddIsHiddenToMessages < ActiveRecord::Migration[5.2]
+ def change
+ add_column :messages, :is_hidden, :boolean, :default => false
+ add_index :messages, :is_hidden
+ end
+end
diff --git a/db/migrate/20190327063241_course_group_count_cache.rb b/db/migrate/20190327063241_course_group_count_cache.rb
new file mode 100644
index 000000000..cf0b868ff
--- /dev/null
+++ b/db/migrate/20190327063241_course_group_count_cache.rb
@@ -0,0 +1,5 @@
+class CourseGroupCountCache < ActiveRecord::Migration[5.2]
+ def change
+
+ end
+end
diff --git a/db/migrate/20190328134047_sync_shixuninfo.rb b/db/migrate/20190328134047_sync_shixuninfo.rb
new file mode 100644
index 000000000..bb374dc29
--- /dev/null
+++ b/db/migrate/20190328134047_sync_shixuninfo.rb
@@ -0,0 +1,8 @@
+class SyncShixuninfo < ActiveRecord::Migration[5.2]
+ def change
+ Shixun.find_each do |shixun|
+ ShixunInfo.create!(propaedeutics: shixun.propaedeutics, description: shixun.description,
+ evaluate_script: shixun.evaluate_script, shixun_id: shixun.id)
+ end
+ end
+end
diff --git a/db/migrate/20190328152957_remove_column_for_shixun.rb b/db/migrate/20190328152957_remove_column_for_shixun.rb
new file mode 100644
index 000000000..70131a4b2
--- /dev/null
+++ b/db/migrate/20190328152957_remove_column_for_shixun.rb
@@ -0,0 +1,5 @@
+class RemoveColumnForShixun < ActiveRecord::Migration[5.2]
+ def change
+ remove_columns :shixuns, :description, :propaedeutics, :evaluate_script
+ end
+end
diff --git a/db/migrate/20190329091942_add_index_to_shixun_infos.rb b/db/migrate/20190329091942_add_index_to_shixun_infos.rb
new file mode 100644
index 000000000..c7a14dfaa
--- /dev/null
+++ b/db/migrate/20190329091942_add_index_to_shixun_infos.rb
@@ -0,0 +1,5 @@
+class AddIndexToShixunInfos < ActiveRecord::Migration[5.2]
+ def change
+ add_index :shixun_infos, :shixun_id, unique: true
+ end
+end
diff --git a/db/migrate/20190401083919_change_quotes_to_integer_in_attachments.rb b/db/migrate/20190401083919_change_quotes_to_integer_in_attachments.rb
new file mode 100644
index 000000000..4e25ab522
--- /dev/null
+++ b/db/migrate/20190401083919_change_quotes_to_integer_in_attachments.rb
@@ -0,0 +1,6 @@
+class ChangeQuotesToIntegerInAttachments < ActiveRecord::Migration[5.2]
+ def change
+ change_column :attachments, :quotes, :integer, :default => 0
+ add_index :attachments, :quotes
+ end
+end
diff --git a/db/migrate/20190401094623_add_index_to_attachments_is_public.rb b/db/migrate/20190401094623_add_index_to_attachments_is_public.rb
new file mode 100644
index 000000000..d098253d0
--- /dev/null
+++ b/db/migrate/20190401094623_add_index_to_attachments_is_public.rb
@@ -0,0 +1,5 @@
+class AddIndexToAttachmentsIsPublic < ActiveRecord::Migration[5.2]
+ def change
+ add_index :attachments, :is_public
+ end
+end
diff --git a/db/migrate/20190402015918_add_invalid_to_graduation_work_score.rb b/db/migrate/20190402015918_add_invalid_to_graduation_work_score.rb
new file mode 100644
index 000000000..5a7ea9c16
--- /dev/null
+++ b/db/migrate/20190402015918_add_invalid_to_graduation_work_score.rb
@@ -0,0 +1,5 @@
+class AddInvalidToGraduationWorkScore < ActiveRecord::Migration[5.2]
+ def change
+ add_column :graduation_work_scores, :is_invalid, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20190402083252_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb b/db/migrate/20190402083252_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb
new file mode 100644
index 000000000..b4bc9a2f6
--- /dev/null
+++ b/db/migrate/20190402083252_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb
@@ -0,0 +1,40 @@
+# This migration comes from acts_as_taggable_on_engine (originally 1)
+if ActiveRecord.gem_version >= Gem::Version.new('5.0')
+ class ActsAsTaggableOnMigration < ActiveRecord::Migration[4.2]; end
+else
+ class ActsAsTaggableOnMigration < ActiveRecord::Migration; end
+end
+ActsAsTaggableOnMigration.class_eval do
+ def self.up
+ unless ActiveRecord::Base.connection.table_exists? 'tags'
+ create_table :tags do |t|
+ t.string :name
+ end
+ end
+
+ unless ActiveRecord::Base.connection.table_exists? 'taggings'
+ create_table :taggings do |t|
+ t.references :tag
+
+ # You should make sure that the column created is
+ # long enough to store the required class names.
+ t.references :taggable, polymorphic: true
+ t.references :tagger, polymorphic: true
+
+ # Limit is created to prevent MySQL error on index
+ # length for MyISAM table type: http://bit.ly/vgW2Ql
+ t.string :context, limit: 128
+
+ t.datetime :created_at
+ end
+ end
+
+ # add_index :taggings, :tag_id
+ # add_index :taggings, [:taggable_id, :taggable_type, :context]
+ end
+
+ def self.down
+ drop_table :taggings
+ drop_table :tags
+ end
+end
diff --git a/db/migrate/20190402083253_add_missing_unique_indices.acts_as_taggable_on_engine.rb b/db/migrate/20190402083253_add_missing_unique_indices.acts_as_taggable_on_engine.rb
new file mode 100644
index 000000000..7206c90e6
--- /dev/null
+++ b/db/migrate/20190402083253_add_missing_unique_indices.acts_as_taggable_on_engine.rb
@@ -0,0 +1,26 @@
+# This migration comes from acts_as_taggable_on_engine (originally 2)
+if ActiveRecord.gem_version >= Gem::Version.new('5.0')
+ class AddMissingUniqueIndices < ActiveRecord::Migration[4.2]; end
+else
+ class AddMissingUniqueIndices < ActiveRecord::Migration; end
+end
+AddMissingUniqueIndices.class_eval do
+ def self.up
+ # add_index :tags, :name, unique: true
+
+ remove_index :taggings, :tag_id if index_exists?(:taggings, :tag_id)
+ remove_index :taggings, [:taggable_id, :taggable_type, :context]
+ add_index :taggings,
+ [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type],
+ unique: true, name: 'taggings_idx'
+ end
+
+ def self.down
+ remove_index :tags, :name
+
+ remove_index :taggings, name: 'taggings_idx'
+
+ add_index :taggings, :tag_id unless index_exists?(:taggings, :tag_id)
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
+ end
+end
diff --git a/db/migrate/20190402083254_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb b/db/migrate/20190402083254_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb
new file mode 100644
index 000000000..5409eef63
--- /dev/null
+++ b/db/migrate/20190402083254_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb
@@ -0,0 +1,20 @@
+# This migration comes from acts_as_taggable_on_engine (originally 3)
+if ActiveRecord.gem_version >= Gem::Version.new('5.0')
+ class AddTaggingsCounterCacheToTags < ActiveRecord::Migration[4.2]; end
+else
+ class AddTaggingsCounterCacheToTags < ActiveRecord::Migration; end
+end
+AddTaggingsCounterCacheToTags.class_eval do
+ def self.up
+ add_column :tags, :taggings_count, :integer, default: 0
+
+ ActsAsTaggableOn::Tag.reset_column_information
+ ActsAsTaggableOn::Tag.find_each do |tag|
+ ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings)
+ end
+ end
+
+ def self.down
+ remove_column :tags, :taggings_count
+ end
+end
diff --git a/db/migrate/20190402083255_add_missing_taggable_index.acts_as_taggable_on_engine.rb b/db/migrate/20190402083255_add_missing_taggable_index.acts_as_taggable_on_engine.rb
new file mode 100644
index 000000000..097600b59
--- /dev/null
+++ b/db/migrate/20190402083255_add_missing_taggable_index.acts_as_taggable_on_engine.rb
@@ -0,0 +1,15 @@
+# This migration comes from acts_as_taggable_on_engine (originally 4)
+if ActiveRecord.gem_version >= Gem::Version.new('5.0')
+ class AddMissingTaggableIndex < ActiveRecord::Migration[4.2]; end
+else
+ class AddMissingTaggableIndex < ActiveRecord::Migration; end
+end
+AddMissingTaggableIndex.class_eval do
+ def self.up
+ add_index :taggings, [:taggable_id, :taggable_type, :context]
+ end
+
+ def self.down
+ remove_index :taggings, [:taggable_id, :taggable_type, :context]
+ end
+end
diff --git a/db/migrate/20190402083256_change_collation_for_tag_names.acts_as_taggable_on_engine.rb b/db/migrate/20190402083256_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
new file mode 100644
index 000000000..731f66c5a
--- /dev/null
+++ b/db/migrate/20190402083256_change_collation_for_tag_names.acts_as_taggable_on_engine.rb
@@ -0,0 +1,15 @@
+# This migration comes from acts_as_taggable_on_engine (originally 5)
+# This migration is added to circumvent issue #623 and have special characters
+# work properly
+if ActiveRecord.gem_version >= Gem::Version.new('5.0')
+ class ChangeCollationForTagNames < ActiveRecord::Migration[4.2]; end
+else
+ class ChangeCollationForTagNames < ActiveRecord::Migration; end
+end
+ChangeCollationForTagNames.class_eval do
+ def up
+ if ActsAsTaggableOn::Utils.using_mysql?
+ execute("ALTER TABLE tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;")
+ end
+ end
+end
diff --git a/db/migrate/20190402083257_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb b/db/migrate/20190402083257_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb
new file mode 100644
index 000000000..7073df2a2
--- /dev/null
+++ b/db/migrate/20190402083257_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb
@@ -0,0 +1,23 @@
+# This migration comes from acts_as_taggable_on_engine (originally 6)
+if ActiveRecord.gem_version >= Gem::Version.new('5.0')
+ class AddMissingIndexesOnTaggings < ActiveRecord::Migration[4.2]; end
+else
+ class AddMissingIndexesOnTaggings < ActiveRecord::Migration; end
+end
+AddMissingIndexesOnTaggings.class_eval do
+ def change
+ add_index :taggings, :tag_id unless index_exists? :taggings, :tag_id
+ add_index :taggings, :taggable_id unless index_exists? :taggings, :taggable_id
+ add_index :taggings, :taggable_type unless index_exists? :taggings, :taggable_type
+ add_index :taggings, :tagger_id unless index_exists? :taggings, :tagger_id
+ add_index :taggings, :context unless index_exists? :taggings, :context
+
+ unless index_exists? :taggings, [:tagger_id, :tagger_type]
+ add_index :taggings, [:tagger_id, :tagger_type]
+ end
+
+ unless index_exists? :taggings, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy'
+ add_index :taggings, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy'
+ end
+ end
+end
diff --git a/db/migrate/20190403022053_create_gtask_banks.rb b/db/migrate/20190403022053_create_gtask_banks.rb
new file mode 100644
index 000000000..f7c7b4ffb
--- /dev/null
+++ b/db/migrate/20190403022053_create_gtask_banks.rb
@@ -0,0 +1,19 @@
+class CreateGtaskBanks < ActiveRecord::Migration[5.2]
+ def change
+ create_table :gtask_banks do |t|
+ t.references :user
+ t.string :name
+ t.text :description
+ t.integer :task_type
+ t.integer :min_num, default: 0
+ t.integer :max_num, default: 0
+ t.integer :base_on_project, default: false
+ t.boolean :is_public, default: false
+ t.integer :quotes, default: 0
+ t.references :graduation_task
+ t.references :course_list
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20190403025525_add_praises_count_for_jouranls_for_messages.rb b/db/migrate/20190403025525_add_praises_count_for_jouranls_for_messages.rb
new file mode 100644
index 000000000..a175d64e5
--- /dev/null
+++ b/db/migrate/20190403025525_add_praises_count_for_jouranls_for_messages.rb
@@ -0,0 +1,13 @@
+class AddPraisesCountForJouranlsForMessages < ActiveRecord::Migration[5.2]
+ def change
+ add_column :journals_for_messages, :praises_count, :integer, :default => 0
+
+ messages = JournalsForMessage.includes(:praise_treads).all
+ messages.find_each do |m|
+ puts("####{m.id}")
+ praises_count = m.praise_treads.liker.count
+ m.update_column(:praises_count, praises_count)
+ end
+
+ end
+end
diff --git a/db/migrate/20190403065609_create_gtopic_banks.rb b/db/migrate/20190403065609_create_gtopic_banks.rb
new file mode 100644
index 000000000..a4e280252
--- /dev/null
+++ b/db/migrate/20190403065609_create_gtopic_banks.rb
@@ -0,0 +1,23 @@
+class CreateGtopicBanks < ActiveRecord::Migration[5.2]
+ def change
+ create_table :gtopic_banks do |t|
+ t.references :user
+ t.string :name
+ t.text :description
+ t.integer :quotes
+ t.boolean :is_public
+ t.integer :topic_type
+ t.integer :topic_source
+ t.integer :topic_property_first
+ t.integer :topic_property_second
+ t.string :source_unit
+ t.integer :topic_repeat
+ t.string :province
+ t.string :city
+ t.references :graduation_topic
+ t.references :course_list
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20190408060723_add_is_ordered_to_exercise_standard_answers.rb b/db/migrate/20190408060723_add_is_ordered_to_exercise_standard_answers.rb
new file mode 100644
index 000000000..8ed2a6f8c
--- /dev/null
+++ b/db/migrate/20190408060723_add_is_ordered_to_exercise_standard_answers.rb
@@ -0,0 +1,6 @@
+class AddIsOrderedToExerciseStandardAnswers < ActiveRecord::Migration[5.2]
+ def change
+ #试题的多空标准答案是否为一一对应,默认为一一对应,即true
+ add_column :exercise_standard_answers,:is_ordered,:boolean,:default => true
+ end
+end
diff --git a/db/migrate/20190410033511_change_column_in_homeworks.rb b/db/migrate/20190410033511_change_column_in_homeworks.rb
new file mode 100644
index 000000000..df8da9b43
--- /dev/null
+++ b/db/migrate/20190410033511_change_column_in_homeworks.rb
@@ -0,0 +1,12 @@
+class ChangeColumnInHomeworks < ActiveRecord::Migration[5.2]
+ def change
+ change_column_default :homework_commons, :late_penalty, from: nil, to: 5
+ change_column_default :homework_commons, :anonymous_comment, from: 0, to: 1
+ change_column_default :homework_commons, :work_efficiency, from: 1, to: 0
+ change_column_default :homework_commons, :eff_score, from: 20, to: 0
+ change_column_default :homework_detail_manuals, :ta_proportion, from: nil, to: 0
+ change_column_default :homework_detail_manuals, :comment_status, from: nil, to: 0
+ change_column_default :homework_detail_manuals, :evaluation_num, from: nil, to: 0
+ change_column_default :homework_detail_manuals, :absence_penalty, from: 1, to: 0
+ end
+end
diff --git a/db/migrate/20190410081421_change_is_ordered_from_answer_to_question.rb b/db/migrate/20190410081421_change_is_ordered_from_answer_to_question.rb
new file mode 100644
index 000000000..ae7144c4e
--- /dev/null
+++ b/db/migrate/20190410081421_change_is_ordered_from_answer_to_question.rb
@@ -0,0 +1,6 @@
+class ChangeIsOrderedFromAnswerToQuestion < ActiveRecord::Migration[5.2]
+ def change
+ remove_column :exercise_standard_answers,:is_ordered
+ add_column :exercise_questions,:is_ordered,:boolean,:default => true
+ end
+end
diff --git a/db/migrate/20190410081736_migrate_homework_attachments.rb b/db/migrate/20190410081736_migrate_homework_attachments.rb
new file mode 100644
index 000000000..fbdc26f6a
--- /dev/null
+++ b/db/migrate/20190410081736_migrate_homework_attachments.rb
@@ -0,0 +1,5 @@
+class MigrateHomeworkAttachments < ActiveRecord::Migration[5.2]
+ def change
+ Attachment.where(container_type: "HomeworkCommon").update_all(attachtype: 1)
+ end
+end
diff --git a/db/migrate/20190413011125_add_cloud_url_to_attachment_histories.rb b/db/migrate/20190413011125_add_cloud_url_to_attachment_histories.rb
new file mode 100644
index 000000000..a2f9db27d
--- /dev/null
+++ b/db/migrate/20190413011125_add_cloud_url_to_attachment_histories.rb
@@ -0,0 +1,5 @@
+class AddCloudUrlToAttachmentHistories < ActiveRecord::Migration[5.2]
+ def change
+ add_column :attachment_histories, :cloud_url, :string, default: ''
+ end
+end
diff --git a/db/migrate/20190415090426_change_homework_common_columns.rb b/db/migrate/20190415090426_change_homework_common_columns.rb
new file mode 100644
index 000000000..11360d47f
--- /dev/null
+++ b/db/migrate/20190415090426_change_homework_common_columns.rb
@@ -0,0 +1,13 @@
+class ChangeHomeworkCommonColumns < ActiveRecord::Migration[5.2]
+ def up
+ change_column :homework_commons, :score_open, :boolean, default: 1
+ change_column :homework_commons, :anonymous_comment, :boolean, default: 1
+ change_column :homework_commons, :anonymous_appeal, :boolean, default: 0
+ end
+
+ def down
+ change_column :homework_commons, :score_open, :int, default: 1
+ change_column :homework_commons, :anonymous_comment, :int, default: 1
+ change_column :homework_commons, :anonymous_appeal, :int, default: 0
+ end
+end
diff --git a/db/migrate/20190422032141_migrate_homework_late_time.rb b/db/migrate/20190422032141_migrate_homework_late_time.rb
new file mode 100644
index 000000000..342986b9c
--- /dev/null
+++ b/db/migrate/20190422032141_migrate_homework_late_time.rb
@@ -0,0 +1,21 @@
+class MigrateHomeworkLateTime < ActiveRecord::Migration[5.2]
+ def change
+ homeworks = HomeworkCommon.where.not(end_time: nil).where(homework_type: [1, 3 ,4], allow_late: 1, late_time: nil)
+ homeworks.each do |homework|
+ if homework.course.try(:end_date).present?
+ homework.update_column("late_time", homework.course.end_date)
+ elsif homework.end_time.present?
+ homework.update_column("late_time", Time.at(homework.end_time.to_i + 30*24*3600))
+ end
+ end
+
+ tasks = GraduationTask.where.not(end_time: nil).where(allow_late: 1, late_time: nil)
+ tasks.each do |task|
+ if task.course.try(:end_date).present?
+ task.update_column("late_time", task.course.end_date)
+ elsif task.end_time.present?
+ task.update_column("late_time", Time.at(task.end_time.to_i + 30*24*3600))
+ end
+ end
+ end
+end
diff --git a/db/migrate/20190422083020_add_journals_for_messages_count_for_hm_and_gt_and_gw.rb b/db/migrate/20190422083020_add_journals_for_messages_count_for_hm_and_gt_and_gw.rb
new file mode 100644
index 000000000..37162c850
--- /dev/null
+++ b/db/migrate/20190422083020_add_journals_for_messages_count_for_hm_and_gt_and_gw.rb
@@ -0,0 +1,18 @@
+class AddJournalsForMessagesCountForHmAndGtAndGw < ActiveRecord::Migration[5.2]
+ def change
+ #add_column :homework_commons, :journals_for_messages_count, :integer, :default => 0
+ #add_column :graduation_tasks, :journals_for_messages_count, :integer, :default => 0
+ #add_column :graduation_topics, :journals_for_messages_count, :integer, :default => 0
+
+ type = ["GraduationTopic", "GraduationTask", "HomeworkCommon"]
+ messages = JournalsForMessage.where(jour_type: type).where("m_parent_id is not null")
+ # 迁移所有2级以上的回复
+ messages.each do |message|
+ while message.parent.try(:m_parent_id).present? do
+ puts "parent: #{message.parent.try(:m_parent_id)}"
+ message.update_attribute(:m_parent_id, message.parent.m_parent_id)
+ end
+ end
+
+ end
+end
diff --git a/db/migrate/20190424014128_migrate_homework_anonymous_comment.rb b/db/migrate/20190424014128_migrate_homework_anonymous_comment.rb
new file mode 100644
index 000000000..21c20fcce
--- /dev/null
+++ b/db/migrate/20190424014128_migrate_homework_anonymous_comment.rb
@@ -0,0 +1,7 @@
+class MigrateHomeworkAnonymousComment < ActiveRecord::Migration[5.2]
+ def change
+ change_column_default :homework_commons, :anonymous_comment, from: 1, to: 0
+
+ HomeworkCommon.update_all("anonymous_comment = !anonymous_comment")
+ end
+end
diff --git a/db/migrate/20190426010412_add_is_invalid_to_student_works_scores.rb b/db/migrate/20190426010412_add_is_invalid_to_student_works_scores.rb
new file mode 100644
index 000000000..2d782f924
--- /dev/null
+++ b/db/migrate/20190426010412_add_is_invalid_to_student_works_scores.rb
@@ -0,0 +1,25 @@
+class AddIsInvalidToStudentWorksScores < ActiveRecord::Migration[5.2]
+ def change
+ add_column :student_works_scores, :is_invalid, :boolean, default: false
+
+ StudentWorksScore.where("score is not null").order("id desc").find_each do |score|
+ unless score.is_invalid
+ if score.student_work.present?
+ puts score.id
+ work = score.student_work
+
+ # 如果分数是调整分 则之前的所有分都无效
+ if score.is_ultimate
+ work.student_works_scores.where("score is not null and id < #{score.id}").update_all(is_invalid: 1)
+
+ # 如果是同一个用户角色的重复评分,则之前的评分都无效
+ elsif work.student_works_scores.where("user_id = #{score.user_id} and reviewer_role = #{score.reviewer_role}
+ and score is not null and id < #{score.id}").count > 0
+ work.student_works_scores.where("user_id = #{score.user_id} and reviewer_role = #{score.reviewer_role}
+ and score is not null and id < #{score.id}").update_all(is_invalid: 1)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/db/migrate/20190427010940_change_exercise_score_to_float.rb b/db/migrate/20190427010940_change_exercise_score_to_float.rb
new file mode 100644
index 000000000..e8c57bc1e
--- /dev/null
+++ b/db/migrate/20190427010940_change_exercise_score_to_float.rb
@@ -0,0 +1,29 @@
+class ChangeExerciseScoreToFloat < ActiveRecord::Migration[5.2]
+ def up
+ change_column :exercise_users,:score,:decimal,precision: 10, scale: 1
+ change_column :exercise_users, :objective_score, :decimal,precision: 10, scale: 1,default: 0.0
+ # change_column :exercise_users, :subjective_score, :decimal,precision: 10, scale: 1,default: 0.0
+ change_column :exercise_users, :subjective_score, :decimal,precision: 10, scale: 1,default: -1.0
+ # change_column :exercise_answers,:score,:decimal,precision: 10, scale: 1,default: 0.0
+ change_column :exercise_answers,:score,:decimal,precision: 10, scale: 1,default: -1.0 #6.10修改,方便判断主观题是否批阅
+ change_column :exercise_bank_questions,:question_score,:decimal,precision: 10, scale: 1
+ change_column :exercise_bank_shixun_challenges,:question_score,:decimal,precision: 10, scale: 1
+ change_column :exercise_questions,:question_score,:decimal,precision: 10, scale: 1
+ change_column :exercise_shixun_answers,:score,:decimal,precision: 10, scale: 1
+ change_column :exercise_shixun_challenges,:question_score,:decimal,precision: 10, scale: 1
+ change_column :exercise_answer_comments, :score,:decimal,precision: 10, scale: 1
+ end
+
+ def down
+ change_column :exercise_users,:score,:integer
+ change_column :exercise_users, :objective_score, :integer,default: -1
+ change_column :exercise_users, :subjective_score, :integer,default: -1
+ change_column :exercise_answers,:score,:integer,default: -1
+ change_column :exercise_bank_questions,:question_score,:integer
+ change_column :exercise_shixun_answers,:score,:integer
+ change_column :exercise_bank_shixun_challenges,:question_score,:integer
+ change_column :exercise_questions,:question_score,:integer
+ change_column :exercise_shixun_challenges,:question_score,:integer
+ change_column :exercise_answer_comments, :score,:integer
+ end
+end
diff --git a/db/migrate/20190427045904_update_exercise_default_value.rb b/db/migrate/20190427045904_update_exercise_default_value.rb
new file mode 100644
index 000000000..05d76ee44
--- /dev/null
+++ b/db/migrate/20190427045904_update_exercise_default_value.rb
@@ -0,0 +1,7 @@
+class UpdateExerciseDefaultValue < ActiveRecord::Migration[5.2]
+ def change
+ ExerciseUser.where(objective_score: -1.0).update_all(objective_score: 0.0)
+ # ExerciseUser.where(subjective_score: -1.0).update_all(subjective_score: 0.0) #主观题默认分数为-1,不需修改
+ # ExerciseAnswer.where(score: -1.0).update_all(score: 0.0) #6.10 修改,方便判断主观题是否
+ end
+end
diff --git a/db/migrate/20190429012219_change_digest_to_string_in_attachment_histories.rb b/db/migrate/20190429012219_change_digest_to_string_in_attachment_histories.rb
new file mode 100644
index 000000000..d7c7e5b14
--- /dev/null
+++ b/db/migrate/20190429012219_change_digest_to_string_in_attachment_histories.rb
@@ -0,0 +1,5 @@
+class ChangeDigestToStringInAttachmentHistories < ActiveRecord::Migration[5.2]
+ def change
+ change_column :attachment_histories, :digest, :string, :limit => 60
+ end
+end
diff --git a/db/migrate/20190429015510_add_default_for_exercises.rb b/db/migrate/20190429015510_add_default_for_exercises.rb
new file mode 100644
index 000000000..3ebbf6e2f
--- /dev/null
+++ b/db/migrate/20190429015510_add_default_for_exercises.rb
@@ -0,0 +1,7 @@
+class AddDefaultForExercises < ActiveRecord::Migration[5.2]
+ def change
+ change_column :exercises, :exercise_status, :integer, :default => 1
+ change_column :exercises, :show_result, :integer, :default => 1
+ change_column :exercises, :time, :integer, :default => -1
+ end
+end
diff --git a/db/migrate/20190429080104_add_quotes_for_graduation_topics.rb b/db/migrate/20190429080104_add_quotes_for_graduation_topics.rb
new file mode 100644
index 000000000..57694008e
--- /dev/null
+++ b/db/migrate/20190429080104_add_quotes_for_graduation_topics.rb
@@ -0,0 +1,5 @@
+class AddQuotesForGraduationTopics < ActiveRecord::Migration[5.2]
+ def change
+ add_column :graduation_topics, :quotes, :integer, :default => 0
+ end
+end
diff --git a/db/migrate/20190430090659_add_default_for_polls.rb b/db/migrate/20190430090659_add_default_for_polls.rb
new file mode 100644
index 000000000..2fcb260ab
--- /dev/null
+++ b/db/migrate/20190430090659_add_default_for_polls.rb
@@ -0,0 +1,5 @@
+class AddDefaultForPolls < ActiveRecord::Migration[5.2]
+ def change
+ change_column :polls, :polls_status, :integer, :default => 1
+ end
+end
diff --git a/db/migrate/20190505031946_sync_users_index.rb b/db/migrate/20190505031946_sync_users_index.rb
new file mode 100644
index 000000000..a1f9b40fb
--- /dev/null
+++ b/db/migrate/20190505031946_sync_users_index.rb
@@ -0,0 +1,8 @@
+class SyncUsersIndex < ActiveRecord::Migration[5.2]
+ def change
+ remove_index :users, name: :index_users_on_phone_and_mail
+ remove_index :users, name: :index_users_on_id_and_type
+ remove_index :users, name: :index_users_on_auth_source_id
+ remove_index :users, name: :index_users_on_type
+ end
+end
diff --git a/db/migrate/20190505072404_remove_index_from_users.rb b/db/migrate/20190505072404_remove_index_from_users.rb
new file mode 100644
index 000000000..b19219346
--- /dev/null
+++ b/db/migrate/20190505072404_remove_index_from_users.rb
@@ -0,0 +1,5 @@
+class RemoveIndexFromUsers < ActiveRecord::Migration[5.2]
+ def change
+ remove_index :users, name: :index_users_on_experience
+ end
+end
diff --git a/db/migrate/20190505092009_delete_ivalid_data.rb b/db/migrate/20190505092009_delete_ivalid_data.rb
new file mode 100644
index 000000000..98f13d9e4
--- /dev/null
+++ b/db/migrate/20190505092009_delete_ivalid_data.rb
@@ -0,0 +1,34 @@
+class DeleteIvalidData < ActiveRecord::Migration[5.2]
+ def change
+ users = User.find_by_sql("select count(*) as user_count, login from users group by login having user_count>1")
+ users.each do |user|
+ valid_users = User.where(login: user.login)
+ valid_users.each do |valid_user|
+ unless valid_user.lastname.present?
+ valid_user.delete
+ end
+ end
+ end
+
+ mail_users = User.find_by_sql("select count(*) as user_count, mail from users where mail is not null group by mail having user_count>1")
+ mail_users.each do |mail_user|
+ valid_mail_users = User.where(mail: mail_user.mail)
+ valid_mail_users.each do |valid_mail_user|
+ unless valid_mail_user.lastname.present?
+ valid_mail_user.delete
+ end
+ end
+ end
+
+
+ phone_users = User.find_by_sql("select count(*) as user_count, phone from users where phone is not null group by phone having user_count>1")
+ phone_users.each do |phone_user|
+ valid_phone_users = User.where(phone: phone_user.phone)
+ valid_phone_users.each do |valid_phone_user|
+ unless valid_phone_user.lastname.present?
+ valid_phone_user.delete
+ end
+ end
+ end
+ end
+end
diff --git a/db/migrate/20190505093440_init_use_index.rb b/db/migrate/20190505093440_init_use_index.rb
new file mode 100644
index 000000000..d885c3a9d
--- /dev/null
+++ b/db/migrate/20190505093440_init_use_index.rb
@@ -0,0 +1,9 @@
+class InitUseIndex < ActiveRecord::Migration[5.2]
+ def change
+ remove_index :users, name: :index_users_on_login if index_exists?(:users, :login, name: :index_users_on_login)
+ remove_index :users, name: :index_users_on_mail if index_exists?(:users, :mail, name: :index_users_on_mail)
+ add_index :users, :login, unique: true
+ add_index :users, :mail, unique: true
+ add_index :users, :phone, unique: true
+ end
+end
diff --git a/db/migrate/20190505095131_add_authentication_and_professional_certification_to_courses.rb b/db/migrate/20190505095131_add_authentication_and_professional_certification_to_courses.rb
new file mode 100644
index 000000000..2cc1ba940
--- /dev/null
+++ b/db/migrate/20190505095131_add_authentication_and_professional_certification_to_courses.rb
@@ -0,0 +1,6 @@
+class AddAuthenticationAndProfessionalCertificationToCourses < ActiveRecord::Migration[5.2]
+ def change
+ add_column :courses, :authentication, :boolean, default: false
+ add_column :courses, :professional_certification, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20190508090916_migrate_new_student_work_status.rb b/db/migrate/20190508090916_migrate_new_student_work_status.rb
new file mode 100644
index 000000000..d2e9bb379
--- /dev/null
+++ b/db/migrate/20190508090916_migrate_new_student_work_status.rb
@@ -0,0 +1,6 @@
+class MigrateNewStudentWorkStatus < ActiveRecord::Migration[5.2]
+ def change
+ homeworks = HomeworkCommon.where(allow_late: 0)
+ StudentWork.where(homework_common_id: homeworks, work_status: 2).update_all(work_status: 1)
+ end
+end
diff --git a/db/migrate/20190511020736_add_is_public_to_messages.rb b/db/migrate/20190511020736_add_is_public_to_messages.rb
new file mode 100644
index 000000000..bbbba8f50
--- /dev/null
+++ b/db/migrate/20190511020736_add_is_public_to_messages.rb
@@ -0,0 +1,5 @@
+class AddIsPublicToMessages < ActiveRecord::Migration[5.2]
+ def change
+ add_column :messages, :is_public, :boolean, :default => false
+ end
+end
diff --git a/db/migrate/20190514023907_add_column_to_zip_packs.rb b/db/migrate/20190514023907_add_column_to_zip_packs.rb
new file mode 100644
index 000000000..8ba250f37
--- /dev/null
+++ b/db/migrate/20190514023907_add_column_to_zip_packs.rb
@@ -0,0 +1,10 @@
+class AddColumnToZipPacks < ActiveRecord::Migration[5.2]
+ def change
+ add_column :zip_packs, :container_id, :integer, default: 0
+ add_column :zip_packs, :container_type, :string
+
+ add_index :zip_packs, [:container_id, :container_type]
+
+ ZipPack.all.update_all("container_type='HomeworkCommon', container_id = homework_id")
+ end
+end
diff --git a/db/migrate/20190514083047_change_question_random_exercises.rb b/db/migrate/20190514083047_change_question_random_exercises.rb
new file mode 100644
index 000000000..313d648bb
--- /dev/null
+++ b/db/migrate/20190514083047_change_question_random_exercises.rb
@@ -0,0 +1,11 @@
+class ChangeQuestionRandomExercises < ActiveRecord::Migration[5.2]
+ def up
+ change_column :exercises,:question_random,:boolean,default: false
+ change_column :exercises,:choice_random,:boolean,default: false
+
+ end
+ def down
+ change_column :exercises,:question_random,:integer,default: 0
+ change_column :exercises,:choice_random,:integer,default: 0
+ end
+end
diff --git a/db/migrate/20190515014734_change_poll_show_result_boolean.rb b/db/migrate/20190515014734_change_poll_show_result_boolean.rb
new file mode 100644
index 000000000..f74dd02c4
--- /dev/null
+++ b/db/migrate/20190515014734_change_poll_show_result_boolean.rb
@@ -0,0 +1,10 @@
+class ChangePollShowResultBoolean < ActiveRecord::Migration[5.2]
+ def change
+ def up
+ change_column :polls,:show_result,:boolean,default: true
+ end
+ def down
+ change_column :polls,:show_result,:integer,default: 1
+ end
+ end
+end
diff --git a/db/migrate/20190517080313_add_test_set_average_for_challenges.rb b/db/migrate/20190517080313_add_test_set_average_for_challenges.rb
new file mode 100644
index 000000000..59fabec9d
--- /dev/null
+++ b/db/migrate/20190517080313_add_test_set_average_for_challenges.rb
@@ -0,0 +1,5 @@
+class AddTestSetAverageForChallenges < ActiveRecord::Migration[5.2]
+ def change
+ add_column :challenges, :test_set_average, :boolean, :default => false
+ end
+end
diff --git a/db/migrate/20190517083326_change_test_set_averger_for_challenges.rb b/db/migrate/20190517083326_change_test_set_averger_for_challenges.rb
new file mode 100644
index 000000000..bf474245f
--- /dev/null
+++ b/db/migrate/20190517083326_change_test_set_averger_for_challenges.rb
@@ -0,0 +1,5 @@
+class ChangeTestSetAvergerForChallenges < ActiveRecord::Migration[5.2]
+ def change
+ change_column :challenges, :test_set_average, :boolean, :default => true
+ end
+end
diff --git a/db/migrate/20190517085407_create_edu_settings.rb b/db/migrate/20190517085407_create_edu_settings.rb
new file mode 100644
index 000000000..e6c081233
--- /dev/null
+++ b/db/migrate/20190517085407_create_edu_settings.rb
@@ -0,0 +1,10 @@
+class CreateEduSettings < ActiveRecord::Migration[5.2]
+ def change
+ create_table :edu_settings do |t|
+ t.string :name
+ t.string :value
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20190517092730_add_description_to_edu_settings.rb b/db/migrate/20190517092730_add_description_to_edu_settings.rb
new file mode 100644
index 000000000..ad273b805
--- /dev/null
+++ b/db/migrate/20190517092730_add_description_to_edu_settings.rb
@@ -0,0 +1,14 @@
+class AddDescriptionToEduSettings < ActiveRecord::Migration[5.2]
+ def change
+ add_column :edu_settings, :description, :string
+ add_index :edu_settings, :name, unique: :true
+
+ ['tomcat_webssh', 'webssh_username', 'webssh_password', 'git_address_ip', 'git_address_domain', 'git_username',
+ 'git_password', 'public_key', 'private_key', 'public_bucket', 'public_bucket_host', 'public_cdn_host', 'cloud_bridge',
+ 'cloud_tomcat_php', 'host_name', 'old_edu_host'].each do |name|
+ if EduSetting.find_by_name(name).nil?
+ EduSetting.create(name: name)
+ end
+ end
+ end
+end
diff --git a/db/migrate/20190518085626_add_index_to_verificatiojn_codes.rb b/db/migrate/20190518085626_add_index_to_verificatiojn_codes.rb
new file mode 100644
index 000000000..e4bdaac19
--- /dev/null
+++ b/db/migrate/20190518085626_add_index_to_verificatiojn_codes.rb
@@ -0,0 +1,6 @@
+class AddIndexToVerificatiojnCodes < ActiveRecord::Migration[5.2]
+ def change
+ add_index :verification_codes, [:phone], name: 'by_phone' if !index_exists?(:verification_codes, :phone)
+ add_index :verification_codes, [:email], name: 'by_email' if !index_exists?(:verification_codes, :email)
+ end
+end
diff --git a/db/migrate/20190522091226_alter_educoder_indexs.rb b/db/migrate/20190522091226_alter_educoder_indexs.rb
new file mode 100644
index 000000000..6e1ed9bb5
--- /dev/null
+++ b/db/migrate/20190522091226_alter_educoder_indexs.rb
@@ -0,0 +1,70 @@
+class AlterEducoderIndexs < ActiveRecord::Migration[5.2]
+ def change
+
+ add_index :onclick_times, :user_id if !index_exists?(:onclick_times, :user_id)
+ remove_index :courses, :syllabus_id
+ add_index :courses, :tea_id
+ add_index :course_modules, [:course_id, :module_type]
+ remove_index :homework_commons, :course_homework_category_id
+ add_index :exercises, :course_id
+ remove_index :teacher_course_groups, :member_id
+ remove_index :student_works, :created_at
+ remove_index :student_works, :user_id
+ remove_index :student_works, :myshixun_id
+ remove_index :homework_challenge_settings, :shixun_id
+ remove_index :exercise_users, :exercise_id
+ add_index :exercise_users, [:exercise_id, :user_id]
+ add_index :homework_banks, :user_id
+ remove_index :exercise_answers, :exercise_choice_id
+ remove_index :exercise_answers, :exercise_question_id
+ add_index :exercise_answers, [:exercise_question_id, :user_id]
+ remove_index :exercise_shixun_challenges, :shixun_id
+ remove_index :exercise_shixun_answers, :exercise_shixun_challenge_id
+ remove_index :exercise_shixun_answers, :exercise_question_id
+ add_index :exercise_shixun_answers, [:exercise_question_id, :user_id], name: "exercise_question_id_user_id"
+ remove_index :student_works_evaluation_distributions, :student_work_id
+ add_index :student_works_evaluation_distributions, :user_id
+ remove_index :challenge_work_scores, :user_id
+ add_index :poll_questions, :poll_id
+ add_index :poll_users, [:poll_id, :user_id]
+ add_index :poll_answers, :poll_question_id
+ add_index :poll_votes, [:poll_question_id, :user_id], name: "poll_question_id_user_id"
+ remove_index :graduation_tasks, :user_id
+ remove_index :graduation_work_comment_assignations, :graduation_group_id
+ remove_index :graduation_works, :course_id
+ remove_index :graduation_works, :project_id
+ remove_index :graduation_works, :graduation_task_id
+ remove_index :graduation_works, :user_id
+ add_index :graduation_works, [:graduation_task_id, :user_id], name: "graduation_task_id_user_id"
+
+ remove_index :graduation_work_scores, :graduation_task_id
+ remove_index :graduation_topics, :user_id
+ remove_index :graduation_topics, :tea_id
+ remove_index :student_graduation_topics, :member_id
+ remove_index :student_graduation_topics, :course_member_id
+ add_index :boards, :course_id
+ remove_index :messages, :author_id
+ remove_index :messages, :is_hidden
+ remove_index :messages, :root_id
+ remove_index :messages, :created_on
+ remove_index :attachments, :quotes
+ remove_index :attachments, :is_public
+ remove_index :attachments, :course_second_category_id
+ remove_index :course_members, :graduation_group_id
+ add_index :student_works_scores_appeals, :student_works_score_id
+ add_index :challenges, :shixun_id
+ add_index :challenge_chooses, :challenge_id
+ add_index :challenge_questions, :challenge_choose_id
+ add_index :challenge_tags, :challenge_id
+ remove_index :games, :user_id
+ add_index :shixun_members, :shixun_id
+ add_index :shixun_tag_repertoires, [:shixun_id, :tag_repertoire_id], name: "shixun_id_tag_repertoire_id"
+ add_index :shixun_schools, :shixun_id
+ add_index :shixun_mirror_repositories, :shixun_id
+ add_index :shixun_modifies, [:shixun_id, :myshixun_id], name: "shixun_id_myshixun_id"
+ remove_index :myshixuns, :user_id
+ remove_index :outputs, :created_at
+ remove_index :outputs, :test_set_position
+ remove_index :stages, :user_id
+ end
+end
diff --git a/db/migrate/20190524020819_change_exercise_question_type.rb b/db/migrate/20190524020819_change_exercise_question_type.rb
new file mode 100644
index 000000000..625a57d06
--- /dev/null
+++ b/db/migrate/20190524020819_change_exercise_question_type.rb
@@ -0,0 +1,10 @@
+class ChangeExerciseQuestionType < ActiveRecord::Migration[5.2]
+ def change
+ ExerciseQuestion.where(question_type: 1).update_all(question_type: 0) #新版0为单选题,1为多选题,2为判断题
+ ExerciseQuestion.where(question_type: 2).update_all(question_type: 1)
+
+ ExerciseBankQuestion.where(question_type: 1).update_all(question_type: 0)
+ ExerciseBankQuestion.where(question_type: 2).update_all(question_type: 1)
+
+ end
+end
diff --git a/db/migrate/20190528024055_migrate_base_on_project.rb b/db/migrate/20190528024055_migrate_base_on_project.rb
new file mode 100644
index 000000000..d0ee197cb
--- /dev/null
+++ b/db/migrate/20190528024055_migrate_base_on_project.rb
@@ -0,0 +1,5 @@
+class MigrateBaseOnProject < ActiveRecord::Migration[5.2]
+ def change
+ change_column :homework_detail_groups, :base_on_project, :boolean, default: true
+ end
+end
diff --git a/db/migrate/20190603024856_migrate_course_late_settings.rb b/db/migrate/20190603024856_migrate_course_late_settings.rb
new file mode 100644
index 000000000..3097d3d34
--- /dev/null
+++ b/db/migrate/20190603024856_migrate_course_late_settings.rb
@@ -0,0 +1,9 @@
+class MigrateCourseLateSettings < ActiveRecord::Migration[5.2]
+ def change
+ change_column_default :homework_commons, :allow_late, from: 1, to: 0
+ change_column_default :homework_commons, :late_penalty, from: 5, to: 0
+
+ change_column_default :graduation_tasks, :allow_late, from: 1, to: 0
+ change_column_default :graduation_tasks, :late_penalty, from: 5, to: 0
+ end
+end
diff --git a/db/migrate/20190605060799_modify_script_and_description_for_shixuninfo.rb b/db/migrate/20190605060799_modify_script_and_description_for_shixuninfo.rb
new file mode 100644
index 000000000..4b3318d56
--- /dev/null
+++ b/db/migrate/20190605060799_modify_script_and_description_for_shixuninfo.rb
@@ -0,0 +1,15 @@
+class ModifyScriptAndDescriptionForShixuninfo < ActiveRecord::Migration[5.2]
+ def change
+ Shixun.find_each do |shixun|
+ if shixun.shixun_info
+ shixun.shixun_info.update_attributes(propaedeutics: shixun[:propaedeutics],
+ description: shixun[:description],
+ evaluate_script: shixun[:evaluate_script],
+ shixun_id: shixun[:id])
+ else
+ ShixunInfo.create!(propaedeutics: shixun[:propaedeutics], description: shixun[:description],
+ evaluate_script: shixun[:evaluate_script], shixun_id: shixun.id)
+ end
+ end
+ end
+end
diff --git a/db/migrate/20190606061948_migrate_student_works_scores_score.rb b/db/migrate/20190606061948_migrate_student_works_scores_score.rb
new file mode 100644
index 000000000..c531357a8
--- /dev/null
+++ b/db/migrate/20190606061948_migrate_student_works_scores_score.rb
@@ -0,0 +1,5 @@
+class MigrateStudentWorksScoresScore < ActiveRecord::Migration[5.2]
+ def change
+ change_column :student_works_scores, :score, :float
+ end
+end
diff --git a/db/migrate/20190610013620_change_question_random_default_true.rb b/db/migrate/20190610013620_change_question_random_default_true.rb
new file mode 100644
index 000000000..d0c785d46
--- /dev/null
+++ b/db/migrate/20190610013620_change_question_random_default_true.rb
@@ -0,0 +1,12 @@
+class ChangeQuestionRandomDefaultTrue < ActiveRecord::Migration[5.2]
+ #试卷的题目和选项的随机显示,需默认勾选
+ def up
+ change_column_default :exercises,:question_random,true
+ change_column_default :exercises,:choice_random,true
+ end
+
+ def down
+ change_column_default :exercises,:question_random,false
+ change_column_default :exercises,:choice_random,false
+ end
+end
diff --git a/db/migrate/20190613022158_add_exec_time_to_challenge.rb b/db/migrate/20190613022158_add_exec_time_to_challenge.rb
new file mode 100644
index 000000000..c1743674c
--- /dev/null
+++ b/db/migrate/20190613022158_add_exec_time_to_challenge.rb
@@ -0,0 +1,9 @@
+class AddExecTimeToChallenge < ActiveRecord::Migration[5.2]
+ def change
+ add_column :challenges, :exec_time, :integer
+
+ Shixun.find_each do |shixun|
+ shixun.challenges.update_all(exec_time: shixun.exec_time)
+ end
+ end
+end
diff --git a/db/migrate/20190617081105_change_default_data_for_name_in_borads.rb b/db/migrate/20190617081105_change_default_data_for_name_in_borads.rb
new file mode 100644
index 000000000..2dbbb7146
--- /dev/null
+++ b/db/migrate/20190617081105_change_default_data_for_name_in_borads.rb
@@ -0,0 +1,10 @@
+class ChangeDefaultDataForNameInBorads < ActiveRecord::Migration[5.2]
+ def change
+ puts "--------- START ------------"
+ Board.roots.joins(:course).where('courses.id = boards.course_id').find_each do |board|
+ board.update_columns(name: '讨论区') unless board.name.strip === '讨论区'
+ puts "Board ID IS #{board.id}"
+ end
+ puts "--------- END ----------"
+ end
+end
diff --git a/db/migrate/20190619055012_add_uniq_index_to_course_modules.rb b/db/migrate/20190619055012_add_uniq_index_to_course_modules.rb
new file mode 100644
index 000000000..92ae6449f
--- /dev/null
+++ b/db/migrate/20190619055012_add_uniq_index_to_course_modules.rb
@@ -0,0 +1,43 @@
+class AddUniqIndexToCourseModules < ActiveRecord::Migration[5.2]
+ def change
+ # sql = %Q(delete from exercise_users where (user_id, exercise_id) in
+ # (select * from (select user_id, exercise_id from exercise_users group by user_id, exercise_id having count(*) > 1) a)
+ # and id not in (select * from (select min(id) from exercise_users group by user_id, exercise_id having count(*) > 1 order by id) b))
+ # ActiveRecord::Base.connection.execute sql
+ #
+ # add_index :exercise_users, [:user_id, :exercise_id], unique: true, name: "index_user_id_and_exercise_id"
+
+
+ sql = %Q(delete from exercise_shixun_answers where (user_id, exercise_shixun_challenge_id) in
+ (select * from (select user_id, exercise_shixun_challenge_id from exercise_shixun_answers group by user_id, exercise_shixun_challenge_id having count(*) > 1) a)
+ and id not in (select * from (select min(id) from exercise_shixun_answers group by user_id, exercise_shixun_challenge_id having count(*) > 1 order by id) b))
+ ActiveRecord::Base.connection.execute sql
+
+ add_index :exercise_shixun_answers, [:user_id, :exercise_shixun_challenge_id], unique: true,
+ name: "index_user_id_and_exercise_shixun_challenge_id"
+
+
+ # sql = %Q(delete from student_works where (user_id, homework_common_id) in
+ # (select * from (select user_id, homework_common_id from student_works group by user_id, homework_common_id having count(*) > 1) a)
+ # and id not in (select * from (select min(id) from student_works group by user_id, homework_common_id having count(*) > 1 order by id) b))
+ # ActiveRecord::Base.connection.execute sql
+ #
+ # add_index :student_works, [:user_id, :homework_common_id], unique: true, name: "index_user_id_and_homework_common_id"
+ #
+ #
+ # sql = %Q(delete from poll_users where (user_id, poll_id) in
+ # (select * from (select user_id, poll_id from poll_users group by user_id, poll_id having count(*) > 1) a) and
+ # id not in (select * from (select min(id) from poll_users group by user_id, poll_id having count(*) > 1 order by id) b))
+ # ActiveRecord::Base.connection.execute sql
+ #
+ # add_index :poll_users, [:user_id, :poll_id], unique: true, name: "index_user_id_and_poll_id"
+ #
+ #
+ # sql = %Q(delete from graduation_works where (user_id, graduation_task_id) in
+ # (select * from (select user_id, graduation_task_id from graduation_works group by user_id, graduation_task_id having count(*) > 1) a) and
+ # id not in (select * from (select min(id) from graduation_works group by user_id, graduation_task_id having count(*) > 1 order by id) b))
+ # ActiveRecord::Base.connection.execute sql
+ #
+ # add_index :graduation_works, [:user_id, :graduation_task_id], unique: true, name: "index_user_id_and_graduation_task_id"
+ end
+end
diff --git a/db/migrate/20190619093200_add_profile_completed_to_users.rb b/db/migrate/20190619093200_add_profile_completed_to_users.rb
new file mode 100644
index 000000000..5248abd58
--- /dev/null
+++ b/db/migrate/20190619093200_add_profile_completed_to_users.rb
@@ -0,0 +1,5 @@
+class AddProfileCompletedToUsers < ActiveRecord::Migration[5.2]
+ def change
+ add_column :users, :profile_completed, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20190619120609_add_graduation_topics_count_to_course.rb b/db/migrate/20190619120609_add_graduation_topics_count_to_course.rb
new file mode 100644
index 000000000..01444b7ce
--- /dev/null
+++ b/db/migrate/20190619120609_add_graduation_topics_count_to_course.rb
@@ -0,0 +1,9 @@
+class AddGraduationTopicsCountToCourse < ActiveRecord::Migration[5.2]
+ def change
+ add_column :courses, :graduation_topics_count, :integer, :default => 0
+ add_column :courses, :graduation_tasks_count, :integer, :default => 0
+ # add_column :courses, :attachments_count, :integer, :default => 0
+ add_column :courses, :polls_count, :integer, :default => 0
+ add_column :courses, :exercises_count, :integer, :default => 0
+ end
+end
diff --git a/db/migrate/20190619123328_sync_countcha_for_course.rb b/db/migrate/20190619123328_sync_countcha_for_course.rb
new file mode 100644
index 000000000..acd9e9716
--- /dev/null
+++ b/db/migrate/20190619123328_sync_countcha_for_course.rb
@@ -0,0 +1,11 @@
+class SyncCountchaForCourse < ActiveRecord::Migration[5.2]
+ def change
+ Course.find_each do |course|
+ Course.reset_counters course.id, :graduation_topics
+ Course.reset_counters course.id, :graduation_tasks
+ Course.reset_counters course.id, :polls
+ Course.reset_counters course.id, :exercises
+ # Course.reset_counters course.id, :attachments
+ end
+ end
+end
diff --git a/db/migrate/20190620010439_add_uniq_index_to_course_member.rb b/db/migrate/20190620010439_add_uniq_index_to_course_member.rb
new file mode 100644
index 000000000..f17cd4274
--- /dev/null
+++ b/db/migrate/20190620010439_add_uniq_index_to_course_member.rb
@@ -0,0 +1,10 @@
+class AddUniqIndexToCourseMember < ActiveRecord::Migration[5.2]
+ def change
+ # sql = %Q(delete from course_members where (user_id, course_id, role) in
+ # (select * from (select user_id, course_id, role from course_members group by user_id, course_id, role having count(*) > 1) a)
+ # and id not in (select * from (select min(id) from course_members group by user_id, course_id, role having count(*) > 1 order by id) b))
+ # ActiveRecord::Base.connection.execute sql
+ #
+ # add_index :course_members, [:user_id, :course_id, :role], unique: true, name: "index_user_id_course_id_role"
+ end
+end
diff --git a/db/migrate/20190620015912_modify_colla_index.rb b/db/migrate/20190620015912_modify_colla_index.rb
new file mode 100644
index 000000000..0e9e23301
--- /dev/null
+++ b/db/migrate/20190620015912_modify_colla_index.rb
@@ -0,0 +1,51 @@
+class ModifyCollaIndex < ActiveRecord::Migration[5.2]
+ def change
+ remove_index :exercise_users, [:user_id, :exercise_id] if index_exists?(:exercise_users, [:user_id, :exercise_id])
+ remove_index :exercise_users, [:exercise_id, :user_id] if index_exists?(:exercise_users, [:exercise_id, :user_id])
+ sql = %Q(delete from exercise_users where (user_id, exercise_id) in
+ (select * from (select user_id, exercise_id from exercise_users group by user_id, exercise_id having count(*) > 1) a)
+ and id not in (select * from (select min(id) from exercise_users group by user_id, exercise_id having count(*) > 1 order by id) b))
+ ActiveRecord::Base.connection.execute sql
+ add_index :exercise_users, [:exercise_id, :user_id], unique: true, name: "index_on_exercise_id_user_id"
+
+
+ remove_index :exercise_shixun_answers, :user_id
+
+
+ remove_index :student_works, [:user_id, :homework_common_id] if index_exists?(:student_works, [:user_id, :homework_common_id])
+ remove_index :student_works, [:homework_common_id, :user_id] if index_exists?(:student_works, [:homework_common_id, :user_id])
+ sql = %Q(delete from student_works where (user_id, homework_common_id) in
+ (select * from (select user_id, homework_common_id from student_works group by user_id, homework_common_id having count(*) > 1) a)
+ and id not in (select * from (select min(id) from student_works group by user_id, homework_common_id having count(*) > 1 order by id) b))
+ ActiveRecord::Base.connection.execute sql
+ add_index :student_works, [:homework_common_id, :user_id], unique: true, name: "index_on_homework_common_id_user_id"
+
+
+ remove_index :poll_users, [:user_id, :poll_id] if index_exists?(:poll_users, [:user_id, :poll_id])
+ remove_index :poll_users, [:poll_id, :user_id] if index_exists?(:poll_users, [:poll_id, :user_id])
+ sql = %Q(delete from poll_users where (user_id, poll_id) in
+ (select * from (select user_id, poll_id from poll_users group by user_id, poll_id having count(*) > 1) a) and
+ id not in (select * from (select min(id) from poll_users group by user_id, poll_id having count(*) > 1 order by id) b))
+ ActiveRecord::Base.connection.execute sql
+ add_index :poll_users, [:poll_id, :user_id], unique: true, name: "index_poll_id_and_user_id"
+
+
+ remove_index :graduation_works, [:user_id, :graduation_task_id] if index_exists?(:graduation_works, [:user_id, :graduation_task_id])
+ remove_index :graduation_works, [:graduation_task_id, :user_id] if index_exists?(:graduation_works, [:graduation_task_id, :user_id])
+ sql = %Q(delete from graduation_works where (user_id, graduation_task_id) in
+ (select * from (select user_id, graduation_task_id from graduation_works group by user_id, graduation_task_id having count(*) > 1) a) and
+ id not in (select * from (select min(id) from graduation_works group by user_id, graduation_task_id having count(*) > 1 order by id) b))
+ ActiveRecord::Base.connection.execute sql
+ add_index :graduation_works, [:graduation_task_id, :user_id], unique: true, name: "index_graduation_task_id_and_user_id"
+
+
+ remove_index :course_members, [:user_id, :course_id, :role] if index_exists?(:course_members, [:user_id, :course_id, :role])
+ remove_index :course_members, :course_id if index_exists?(:course_members, :course_id)
+ remove_index :course_members, :user_id if index_exists?(:course_members, :user_id)
+ sql = %Q(delete from course_members where (course_id, user_id, role) in
+ (select * from (select course_id, user_id, role from course_members group by course_id, user_id, role having count(*) > 1) a)
+ and id not in (select * from (select min(id) from course_members group by course_id, user_id, role having count(*) > 1 order by id) b))
+ ActiveRecord::Base.connection.execute sql
+ add_index :course_members, [:course_id, :user_id, :role], unique: true, name: "index_course_id_user_id_role"
+ end
+end
diff --git a/db/migrate/20190620021243_add_homework_bank_id_index_to_homework_commons.rb b/db/migrate/20190620021243_add_homework_bank_id_index_to_homework_commons.rb
new file mode 100644
index 000000000..9a97b82bf
--- /dev/null
+++ b/db/migrate/20190620021243_add_homework_bank_id_index_to_homework_commons.rb
@@ -0,0 +1,5 @@
+class AddHomeworkBankIdIndexToHomeworkCommons < ActiveRecord::Migration[5.2]
+ def change
+ add_index :homework_commons, :homework_bank_id
+ end
+end
diff --git a/db/migrate/20190620075503_add_bank_id_for_graduation_task_and_topic.rb b/db/migrate/20190620075503_add_bank_id_for_graduation_task_and_topic.rb
new file mode 100644
index 000000000..8149731e7
--- /dev/null
+++ b/db/migrate/20190620075503_add_bank_id_for_graduation_task_and_topic.rb
@@ -0,0 +1,6 @@
+class AddBankIdForGraduationTaskAndTopic < ActiveRecord::Migration[5.2]
+ def change
+ add_column :graduation_tasks, :gtask_bank_id, :integer
+ add_column :graduation_topics, :gtopic_bank_id, :integer
+ end
+end
diff --git a/db/seeds.rb b/db/seeds.rb
new file mode 100644
index 000000000..bc60c4ac6
--- /dev/null
+++ b/db/seeds.rb
@@ -0,0 +1,18 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
+#
+# Examples:
+#
+# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
+# Character.create(name: 'Luke', movie: movies.first)
+
+# create_table :outputs do |t|
+# t.integer :code
+# t.integer :game_id
+# t.text :msg
+# t.longtext :out_put
+# t.integer :test_set_position
+# t.text :actual_output
+#
+# t.timestamps
+# end
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
new file mode 100644
index 000000000..dd64cbec2
--- /dev/null
+++ b/db/structure.sql
@@ -0,0 +1,6778 @@
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+DROP TABLE IF EXISTS `activities`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `activities` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `act_id` int(11) NOT NULL,
+ `act_type` varchar(255) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `activity_container_id` int(11) DEFAULT NULL,
+ `activity_container_type` varchar(255) DEFAULT '',
+ `created_at` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_activities_on_user_id` (`user_id`),
+ KEY `index_activities_on_act_id_and_act_type` (`act_id`,`act_type`),
+ KEY `index_activities_on_user_id_and_act_type` (`user_id`,`act_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=124468 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `activity_notifies`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `activity_notifies` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `activity_container_id` int(11) DEFAULT NULL,
+ `activity_container_type` varchar(255) DEFAULT NULL,
+ `activity_id` int(11) DEFAULT NULL,
+ `activity_type` varchar(255) DEFAULT NULL,
+ `notify_to` int(11) DEFAULT NULL,
+ `created_on` datetime DEFAULT NULL,
+ `is_read` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_an_notify_to` (`notify_to`),
+ KEY `index_an_created_on` (`created_on`),
+ KEY `index_an_activity_container_id` (`activity_container_id`,`activity_container_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=1294 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `api_keys`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `api_keys` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `access_token` varchar(255) DEFAULT NULL,
+ `expires_at` datetime DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `active` tinyint(1) DEFAULT '1',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_api_keys_on_user_id` (`user_id`),
+ KEY `index_api_keys_on_access_token` (`access_token`)
+) ENGINE=InnoDB AUTO_INCREMENT=2906 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `applied_contests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `applied_contests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `contest_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `role` varchar(255) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_applied_contests_on_contest_id` (`contest_id`),
+ KEY `index_applied_contests_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=112 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `applied_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `applied_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `applied_id` int(11) DEFAULT NULL,
+ `applied_type` varchar(255) DEFAULT NULL,
+ `viewed` int(11) DEFAULT '0',
+ `status` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `applied_user_id` int(11) DEFAULT NULL,
+ `role` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=6189 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `applied_projects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `applied_projects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `role` int(11) DEFAULT '0',
+ `status` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=859 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `apply_actions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `apply_actions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `reason` varchar(255) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `dealer_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `status` tinyint(4) DEFAULT '0',
+ `apply_reason` text,
+ `noticed` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=11512 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `apply_add_departments`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `apply_add_departments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `department_id` int(11) DEFAULT NULL,
+ `school_id` int(11) DEFAULT NULL,
+ `remarks` text,
+ `user_id` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_apply_add_departments_on_department_id` (`department_id`),
+ KEY `index_apply_add_departments_on_school_id` (`school_id`),
+ KEY `index_apply_add_departments_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=734 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `apply_add_schools`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `apply_add_schools` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `province` varchar(255) DEFAULT NULL,
+ `city` varchar(255) DEFAULT NULL,
+ `address` varchar(255) DEFAULT NULL,
+ `remarks` varchar(255) DEFAULT NULL,
+ `school_id` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1024 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `apply_homeworks`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `apply_homeworks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `status` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_apply_homeworks_on_user_id` (`user_id`),
+ KEY `index_apply_homeworks_on_homework_common_id` (`homework_common_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `apply_project_masters`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `apply_project_masters` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `apply_type` varchar(255) DEFAULT NULL,
+ `apply_id` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `apply_resources`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `apply_resources` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `status` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `attachment_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `content` text,
+ `apply_user_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `apply_user_authentications`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `apply_user_authentications` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `auth_type` int(11) DEFAULT NULL,
+ `remarks` varchar(255) DEFAULT NULL,
+ `dealer` int(11) DEFAULT NULL,
+ `deal_time` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `is_delete` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_apply_user_authentications_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1257 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `article_homepages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `article_homepages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `title` varchar(255) DEFAULT NULL,
+ `content` text,
+ `user_id` int(11) DEFAULT NULL,
+ `homepage_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_article_homepages_on_user_id` (`user_id`),
+ KEY `index_article_homepages_on_homepage_id` (`homepage_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1632 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `at_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `at_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `at_message_id` int(11) DEFAULT NULL,
+ `at_message_type` varchar(255) DEFAULT NULL,
+ `viewed` tinyint(1) DEFAULT '0',
+ `container_type` varchar(255) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `sender_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_at_messages_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4562 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `attachment_group_settings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `attachment_group_settings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `attachment_id` int(11) DEFAULT NULL,
+ `course_group_id` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `publish_time` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_attachment_group_settings_on_attachment_id` (`attachment_id`),
+ KEY `index_attachment_group_settings_on_course_group_id` (`course_group_id`),
+ KEY `index_attachment_group_settings_on_course_id` (`course_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `attachment_histories`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `attachment_histories` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `filename` varchar(255) DEFAULT '',
+ `disk_filename` varchar(255) DEFAULT '',
+ `filesize` int(11) DEFAULT '0',
+ `content_type` varchar(255) DEFAULT '',
+ `digest` varchar(40) DEFAULT '',
+ `downloads` int(11) DEFAULT '0',
+ `author_id` int(11) DEFAULT NULL,
+ `created_on` datetime DEFAULT NULL,
+ `description` text,
+ `disk_directory` varchar(255) DEFAULT NULL,
+ `attachtype` int(11) DEFAULT NULL,
+ `is_public` int(11) DEFAULT NULL,
+ `copy_from` int(11) DEFAULT NULL,
+ `quotes` int(11) DEFAULT NULL,
+ `version` int(11) DEFAULT NULL,
+ `attachment_id` int(11) DEFAULT NULL,
+ `is_publish` int(11) DEFAULT '1',
+ `publish_time` date DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=358 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `attachments`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `attachments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(30) DEFAULT NULL,
+ `filename` varchar(255) NOT NULL DEFAULT '',
+ `disk_filename` varchar(255) NOT NULL DEFAULT '',
+ `filesize` int(11) NOT NULL DEFAULT '0',
+ `content_type` varchar(255) DEFAULT '',
+ `digest` varchar(40) NOT NULL DEFAULT '',
+ `downloads` int(11) NOT NULL DEFAULT '0',
+ `author_id` int(11) NOT NULL DEFAULT '0',
+ `created_on` datetime DEFAULT NULL,
+ `description` text,
+ `disk_directory` varchar(255) DEFAULT NULL,
+ `attachtype` int(11) DEFAULT '1',
+ `is_public` int(11) DEFAULT '1',
+ `copy_from` int(11) DEFAULT NULL,
+ `quotes` int(11) DEFAULT NULL,
+ `is_publish` int(11) DEFAULT '1',
+ `publish_time` datetime DEFAULT NULL,
+ `resource_bank_id` int(11) DEFAULT NULL,
+ `unified_setting` tinyint(1) DEFAULT '1',
+ PRIMARY KEY (`id`),
+ KEY `index_attachments_on_author_id` (`author_id`),
+ KEY `index_attachments_on_created_on` (`created_on`),
+ KEY `index_attachments_on_container_id_and_container_type` (`container_id`,`container_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=206533 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `attachmentstypes`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `attachmentstypes` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `typeId` int(11) NOT NULL,
+ `typeName` varchar(50) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `attendances`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `attendances` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `score` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `user` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24510 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `auth_sources`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `auth_sources` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `type` varchar(30) NOT NULL DEFAULT '',
+ `name` varchar(60) NOT NULL DEFAULT '',
+ `host` varchar(60) DEFAULT NULL,
+ `port` int(11) DEFAULT NULL,
+ `account` varchar(255) DEFAULT NULL,
+ `account_password` varchar(255) DEFAULT '',
+ `base_dn` varchar(255) DEFAULT NULL,
+ `attr_login` varchar(30) DEFAULT NULL,
+ `attr_firstname` varchar(30) DEFAULT NULL,
+ `attr_lastname` varchar(30) DEFAULT NULL,
+ `attr_mail` varchar(30) DEFAULT NULL,
+ `onthefly_register` tinyint(1) NOT NULL DEFAULT '0',
+ `tls` tinyint(1) NOT NULL DEFAULT '0',
+ `filter` varchar(255) DEFAULT NULL,
+ `timeout` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_auth_sources_on_id_and_type` (`id`,`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `authentications`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `authentications` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `level` tinyint(4) DEFAULT NULL,
+ `permissions` text,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `authentications_users`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `authentications_users` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `authentication_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1949 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `biding_projects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `biding_projects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `bid_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `reward` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `bids`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `bids` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `budget` varchar(255) NOT NULL,
+ `author_id` int(11) DEFAULT NULL,
+ `deadline` date DEFAULT NULL,
+ `description` text,
+ `created_on` datetime NOT NULL,
+ `updated_on` datetime NOT NULL,
+ `commit` int(11) DEFAULT NULL,
+ `reward_type` int(11) DEFAULT NULL,
+ `homework_type` int(11) DEFAULT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `password` varchar(255) DEFAULT NULL,
+ `is_evaluation` int(11) DEFAULT NULL,
+ `proportion` int(11) DEFAULT '60',
+ `comment_status` int(11) DEFAULT '0',
+ `evaluation_num` int(11) DEFAULT '3',
+ `open_anonymous_evaluation` int(11) DEFAULT '1',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=761 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `blog_comments`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `blog_comments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `blog_id` int(11) NOT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `title` varchar(255) NOT NULL DEFAULT '',
+ `content` text,
+ `author_id` int(11) DEFAULT NULL,
+ `comments_count` int(11) NOT NULL DEFAULT '0',
+ `last_comment_id` int(11) DEFAULT NULL,
+ `created_on` datetime NOT NULL,
+ `updated_on` datetime NOT NULL,
+ `locked` tinyint(1) DEFAULT '0',
+ `sticky` int(11) DEFAULT '0',
+ `reply_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `root_id` int(11) DEFAULT NULL,
+ `visits` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_blog_comments_on_root_id` (`root_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=620 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `blog_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `blog_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `blog_message_id` int(11) DEFAULT NULL,
+ `blog_id` int(11) DEFAULT NULL,
+ `blog_message_type` varchar(255) DEFAULT NULL,
+ `viewed` tinyint(1) DEFAULT '0',
+ `content` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `user_operator_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `blogs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `blogs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `description` text,
+ `position` int(11) DEFAULT '1',
+ `article_count` int(11) NOT NULL DEFAULT '0',
+ `comments_count` int(11) NOT NULL DEFAULT '0',
+ `last_comments_id` int(11) DEFAULT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `author_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `homepage_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24115 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `boards`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `boards` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) NOT NULL,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `description` varchar(255) DEFAULT NULL,
+ `position` int(11) DEFAULT '1',
+ `topics_count` int(11) NOT NULL DEFAULT '0',
+ `messages_count` int(11) NOT NULL DEFAULT '0',
+ `last_message_id` int(11) DEFAULT NULL,
+ `parent_id` int(11) DEFAULT '0',
+ `course_id` int(11) DEFAULT NULL,
+ `org_subfield_id` int(11) DEFAULT NULL,
+ `contest_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `boards_project_id` (`project_id`),
+ KEY `index_boards_on_last_message_id` (`last_message_id`),
+ KEY `parent` (`parent_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5852 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `bug_to_osps`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `bug_to_osps` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `osp_id` int(11) DEFAULT NULL,
+ `relative_memo_id` int(11) DEFAULT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `challenge_chooses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `challenge_chooses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `subject` text,
+ `challenge_id` int(11) DEFAULT NULL,
+ `standard_answer` varchar(255) DEFAULT NULL,
+ `answer` text,
+ `score` int(11) DEFAULT NULL,
+ `difficult` int(11) DEFAULT '1',
+ `category` int(11) DEFAULT NULL,
+ `position` int(11) DEFAULT '1',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=277 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `challenge_questions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `challenge_questions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `option_name` text,
+ `challenge_choose_id` int(11) DEFAULT NULL,
+ `right_key` tinyint(1) DEFAULT NULL,
+ `position` tinyint(4) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2146 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `challenge_samples`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `challenge_samples` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `input` varchar(255) DEFAULT NULL,
+ `output` varchar(255) DEFAULT NULL,
+ `challenge_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `game_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `challenge_tags`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `challenge_tags` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `challenge_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `challenge_choose_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13972 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `challenges`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `challenges` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `subject` varchar(255) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `status` tinyint(4) DEFAULT '0',
+ `position` tinyint(4) DEFAULT '1',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `task_pass` longtext,
+ `answer` longtext,
+ `score` int(11) DEFAULT '100',
+ `visits` int(11) DEFAULT '0',
+ `path` varchar(255) DEFAULT NULL,
+ `evaluation_way` int(11) DEFAULT '0',
+ `difficulty` int(11) DEFAULT '1',
+ `exec_path` varchar(255) DEFAULT NULL,
+ `code_line` int(11) DEFAULT NULL,
+ `st` tinyint(4) DEFAULT '0',
+ `web_route` text,
+ `picture_path` text,
+ `expect_picture_path` text,
+ `modify_time` datetime DEFAULT NULL,
+ `challenge_tags_count` int(11) DEFAULT '0',
+ `original_picture_path` varchar(255) DEFAULT NULL,
+ `show_type` int(11) DEFAULT '-1',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3286 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `changes`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `changes` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `changeset_id` int(11) NOT NULL,
+ `action` varchar(1) NOT NULL DEFAULT '',
+ `path` text NOT NULL,
+ `from_path` text,
+ `from_revision` varchar(255) DEFAULT NULL,
+ `revision` varchar(255) DEFAULT NULL,
+ `branch` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `changesets_changeset_id` (`changeset_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1128132 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `changeset_parents`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `changeset_parents` (
+ `changeset_id` int(11) NOT NULL,
+ `parent_id` int(11) NOT NULL,
+ KEY `changeset_parents_changeset_ids` (`changeset_id`),
+ KEY `changeset_parents_parent_ids` (`parent_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `changesets`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `changesets` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `repository_id` int(11) NOT NULL,
+ `revision` varchar(255) NOT NULL,
+ `committer` varchar(255) DEFAULT NULL,
+ `committed_on` datetime NOT NULL,
+ `comments` text,
+ `commit_date` date DEFAULT NULL,
+ `scmid` varchar(255) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `type` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `changesets_repos_rev` (`repository_id`,`revision`),
+ KEY `index_changesets_on_user_id` (`user_id`),
+ KEY `index_changesets_on_repository_id` (`repository_id`),
+ KEY `index_changesets_on_committed_on` (`committed_on`),
+ KEY `changesets_repos_scmid` (`repository_id`,`scmid`)
+) ENGINE=InnoDB AUTO_INCREMENT=121390 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `changesets_issues`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `changesets_issues` (
+ `changeset_id` int(11) NOT NULL,
+ `issue_id` int(11) NOT NULL,
+ UNIQUE KEY `changesets_issues_ids` (`changeset_id`,`issue_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `chart_rules`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `chart_rules` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `rule_type` varchar(255) DEFAULT NULL,
+ `content` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `choose_outputs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `choose_outputs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `challenge_choose_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `answer` varchar(255) DEFAULT NULL,
+ `correct` tinyint(1) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=71 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `code_tests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `code_tests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `homework_id` int(11) DEFAULT NULL,
+ `wait_time` int(11) DEFAULT '0',
+ `language` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `time_used` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `student_work_id` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=759267 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `comments`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `comments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `commented_type` varchar(30) NOT NULL DEFAULT '',
+ `commented_id` int(11) NOT NULL DEFAULT '0',
+ `author_id` int(11) NOT NULL DEFAULT '0',
+ `comments` text,
+ `created_on` datetime NOT NULL,
+ `updated_on` datetime NOT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `comments_count` int(11) DEFAULT '0',
+ `reply_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_comments_on_commented_id_and_commented_type` (`commented_id`,`commented_type`),
+ KEY `index_comments_on_author_id` (`author_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4799 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `commit_issues`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `commit_issues` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `commit_id` varchar(255) DEFAULT NULL,
+ `issue_id` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `commits`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `commits` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `repository_id` int(11) DEFAULT NULL,
+ `version` varchar(255) DEFAULT NULL,
+ `committer` varchar(255) DEFAULT NULL,
+ `comments` text,
+ `committed_on` datetime DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `competition_entries`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `competition_entries` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `competition_stage_section_id` int(11) DEFAULT NULL,
+ `competition_stage_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `url` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_competition_entries_on_competition_stage_section_id` (`competition_stage_section_id`),
+ KEY `index_competition_entries_on_competition_stage_id` (`competition_stage_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `competition_lists`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `competition_lists` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `competition_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_competition_lists_on_competition_id` (`competition_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `competition_modules`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `competition_modules` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `competition_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `hidden` tinyint(1) DEFAULT '0',
+ `url` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_competition_modules_on_competition_id` (`competition_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `competition_scores`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `competition_scores` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `score` float DEFAULT NULL,
+ `score_type` varchar(255) DEFAULT NULL,
+ `region` varchar(255) DEFAULT NULL,
+ `cost_time` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_competition_scores_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=337 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `competition_stage_sections`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `competition_stage_sections` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `competition_id` int(11) DEFAULT NULL,
+ `competition_stage_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `start_time` datetime DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `entry` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_competition_stage_sections_on_competition_id` (`competition_id`),
+ KEY `index_competition_stage_sections_on_competition_stage_id` (`competition_stage_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `competition_stages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `competition_stages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `competition_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_competition_stages_on_competition_id` (`competition_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `competitions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `competitions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `start_time` datetime DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `identifier` varchar(255) DEFAULT NULL,
+ `status` tinyint(1) DEFAULT '0',
+ `online_time` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `visits` int(11) DEFAULT '0',
+ `competition_lists_count` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contest_activities`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contest_activities` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `contest_id` int(11) DEFAULT NULL,
+ `contest_act_id` int(11) DEFAULT NULL,
+ `contest_act_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_contest_activities_on_user_id` (`user_id`),
+ KEY `index_contest_activities_on_contest_id` (`contest_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contest_member_roles`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contest_member_roles` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `contest_member_id` int(11) DEFAULT NULL,
+ `role_id` int(11) DEFAULT NULL,
+ `is_current` tinyint(1) DEFAULT '1',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_contest_member_roles_on_contest_member_id` (`contest_member_id`),
+ KEY `index_contest_member_roles_on_role_id` (`role_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=185 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contest_members`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contest_members` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `contest_id` int(11) DEFAULT NULL,
+ `is_collect` tinyint(1) DEFAULT '1',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_contest_members_on_user_id` (`user_id`),
+ KEY `index_contest_members_on_contest_id` (`contest_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=175 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contest_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contest_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `contest_id` int(11) DEFAULT NULL,
+ `contest_message_id` int(11) DEFAULT NULL,
+ `contest_message_type` varchar(255) DEFAULT NULL,
+ `viewed` tinyint(1) DEFAULT '0',
+ `content` text,
+ `status` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_contest_messages_on_user_id` (`user_id`),
+ KEY `index_contest_messages_on_contest_id` (`contest_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1983 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contestant_for_contests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contestant_for_contests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `student_id` int(11) DEFAULT NULL,
+ `contest_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_contestant_for_contests_on_contest_id` (`contest_id`),
+ KEY `index_contestant_for_contests_on_student_id` (`student_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=129 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contestant_work_evaluation_distributions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contestant_work_evaluation_distributions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `contestant_work_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_contestant_work_evaluation_distributions_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contestant_work_projects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contestant_work_projects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `contest_id` int(11) DEFAULT NULL,
+ `work_id` int(11) DEFAULT NULL,
+ `contestant_work_id` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `is_leader` tinyint(1) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_contestant_work_projects_on_contest_id` (`contest_id`),
+ KEY `index_contestant_work_projects_on_work_id` (`work_id`),
+ KEY `index_contestant_work_projects_on_contestant_work_id` (`contestant_work_id`),
+ KEY `index_contestant_work_projects_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=83 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contestant_work_scores`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contestant_work_scores` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `contestant_work_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `score` int(11) DEFAULT NULL,
+ `comment` text,
+ `reviewer_role` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_contestant_work_scores_on_contestant_work_id` (`contestant_work_id`),
+ KEY `index_contestant_work_scores_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contestant_works`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contestant_works` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `work_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `work_score` float DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `work_status` int(11) DEFAULT NULL,
+ `commit_time` datetime DEFAULT NULL,
+ `is_delete` tinyint(1) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `judge_score` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_contestant_works_on_work_id` (`work_id`),
+ KEY `index_contestant_works_on_user_id` (`user_id`),
+ KEY `index_contestant_works_on_project_id` (`project_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=114 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `contests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `contests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `is_public` tinyint(1) DEFAULT NULL,
+ `is_delete` tinyint(1) DEFAULT '0',
+ `visits` int(11) DEFAULT '0',
+ `invite_code` varchar(255) DEFAULT NULL,
+ `invite_code_halt` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=810 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `coo_imgs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `coo_imgs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `src_states` varchar(255) DEFAULT NULL,
+ `url_states` varchar(255) DEFAULT NULL,
+ `img_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `position` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `coop_imgs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `coop_imgs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `src_states` varchar(255) DEFAULT NULL,
+ `url_states` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `img_type` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `cooperations`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `cooperations` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `mail` varchar(255) DEFAULT NULL,
+ `qq` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `user_type` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_activities`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_activities` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `course_act_id` int(11) DEFAULT NULL,
+ `course_act_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `course_act_index` (`course_id`,`course_act_id`,`course_act_type`,`created_at`)
+) ENGINE=InnoDB AUTO_INCREMENT=38842 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_attachments`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_attachments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `filename` varchar(255) DEFAULT NULL,
+ `disk_filename` varchar(255) DEFAULT NULL,
+ `filesize` int(11) DEFAULT NULL,
+ `content_type` varchar(255) DEFAULT NULL,
+ `digest` varchar(255) DEFAULT NULL,
+ `downloads` int(11) DEFAULT NULL,
+ `author_id` varchar(255) DEFAULT NULL,
+ `integer` varchar(255) DEFAULT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `disk_directory` varchar(255) DEFAULT NULL,
+ `attachtype` int(11) DEFAULT NULL,
+ `is_public` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `container_id` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=413 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_contributor_scores`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_contributor_scores` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `course_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `message_num` int(11) DEFAULT '0',
+ `message_reply_num` int(11) DEFAULT '0',
+ `news_reply_num` int(11) DEFAULT '0',
+ `resource_num` int(11) DEFAULT '0',
+ `journal_num` int(11) DEFAULT '0',
+ `journal_reply_num` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `total_score` int(11) DEFAULT '0',
+ `homework_journal_num` int(11) DEFAULT '0',
+ `news_num` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_course_contributor_scores_on_course_id_and_user_id` (`course_id`,`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=34579 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_groups`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_groups` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `members_count` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_course_groups_on_course_id` (`course_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1009 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_homework_statistics`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_homework_statistics` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `committed_work_num` int(11) DEFAULT '0',
+ `un_commit_work_num` int(11) DEFAULT '0',
+ `late_commit_work_num` int(11) DEFAULT '0',
+ `absence_evaluation_work_num` int(11) DEFAULT '0',
+ `un_evaluation_work_num` int(11) DEFAULT '0',
+ `appeal_num` int(11) DEFAULT '0',
+ `average_score` float DEFAULT '0',
+ `total_score` float DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=31032 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_infos`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_infos` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `course_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1417 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_lists`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_lists` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `is_admin` tinyint(1) DEFAULT '1',
+ `support_shixuns_search` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1178 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `course_message_id` int(11) DEFAULT NULL,
+ `course_message_type` varchar(255) DEFAULT NULL,
+ `viewed` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `content` text,
+ `status` int(11) DEFAULT NULL,
+ `apply_user_id` int(11) DEFAULT NULL,
+ `apply_result` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_course_messages_on_user_id_and_course_id_and_created_at` (`user_id`,`course_id`,`created_at`),
+ KEY `index_course_messages_on_course_message_type` (`course_message_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=1032687 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_modules`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_modules` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `course_id` int(11) DEFAULT NULL,
+ `module_type` varchar(255) DEFAULT NULL,
+ `position` int(11) DEFAULT NULL,
+ `hidden` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `module_name` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=12718 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `course_statuses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `course_statuses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `changesets_count` int(11) DEFAULT NULL,
+ `watchers_count` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `grade` float DEFAULT '0',
+ `course_ac_para` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=666 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `courses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `courses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `tea_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `state` int(11) DEFAULT NULL,
+ `code` varchar(255) DEFAULT NULL,
+ `time` int(11) DEFAULT NULL,
+ `extra` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `location` varchar(255) DEFAULT NULL,
+ `term` varchar(255) DEFAULT NULL,
+ `string` varchar(255) DEFAULT NULL,
+ `password` varchar(255) DEFAULT NULL,
+ `setup_time` varchar(255) DEFAULT NULL,
+ `endup_time` varchar(255) DEFAULT NULL,
+ `class_period` varchar(255) DEFAULT NULL,
+ `school_id` int(11) DEFAULT NULL,
+ `description` text,
+ `status` int(11) DEFAULT '1',
+ `attachmenttype` int(11) DEFAULT '2',
+ `lft` int(11) DEFAULT NULL,
+ `rgt` int(11) DEFAULT NULL,
+ `is_public` tinyint(4) DEFAULT '1',
+ `inherit_members` tinyint(4) DEFAULT '1',
+ `open_student` int(11) DEFAULT '0',
+ `outline` int(11) DEFAULT '0',
+ `publish_resource` int(11) DEFAULT '0',
+ `is_delete` int(11) DEFAULT '0',
+ `end_time` int(11) DEFAULT NULL,
+ `end_term` varchar(255) DEFAULT NULL,
+ `is_excellent` int(11) DEFAULT '0',
+ `excellent_option` int(11) DEFAULT '0',
+ `is_copy` int(11) DEFAULT '0',
+ `visits` int(11) DEFAULT '0',
+ `syllabus_id` int(11) DEFAULT NULL,
+ `invite_code` varchar(255) DEFAULT NULL,
+ `qrcode` varchar(255) DEFAULT NULL,
+ `qrcode_expiretime` int(11) DEFAULT '0',
+ `invite_code_halt` tinyint(4) DEFAULT '0',
+ `os_allow` int(11) DEFAULT '0',
+ `credit` float DEFAULT NULL,
+ `is_end` tinyint(1) DEFAULT '0',
+ `end_date` date DEFAULT NULL,
+ `choose_group_allow` tinyint(1) DEFAULT '0',
+ `homepage_show` tinyint(1) DEFAULT '0',
+ `course_list_id` int(11) DEFAULT NULL,
+ `members_count` int(11) DEFAULT '0',
+ `homework_commons_count` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `index_courses_on_invite_code` (`invite_code`),
+ KEY `index_courses_on_syllabus_id` (`syllabus_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1439 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `custom_fields`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `custom_fields` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `type` varchar(30) NOT NULL DEFAULT '',
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `field_format` varchar(30) NOT NULL DEFAULT '',
+ `possible_values` text,
+ `regexp` varchar(255) DEFAULT '',
+ `min_length` int(11) NOT NULL DEFAULT '0',
+ `max_length` int(11) NOT NULL DEFAULT '0',
+ `is_required` tinyint(1) NOT NULL DEFAULT '0',
+ `is_for_all` tinyint(1) NOT NULL DEFAULT '0',
+ `is_filter` tinyint(1) NOT NULL DEFAULT '0',
+ `position` int(11) DEFAULT '1',
+ `searchable` tinyint(1) DEFAULT '0',
+ `default_value` text,
+ `editable` tinyint(1) DEFAULT '1',
+ `visible` tinyint(1) NOT NULL DEFAULT '1',
+ `multiple` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_custom_fields_on_id_and_type` (`id`,`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `custom_fields_projects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `custom_fields_projects` (
+ `custom_field_id` int(11) NOT NULL DEFAULT '0',
+ `project_id` int(11) NOT NULL DEFAULT '0',
+ UNIQUE KEY `index_custom_fields_projects_on_custom_field_id_and_project_id` (`custom_field_id`,`project_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `custom_fields_trackers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `custom_fields_trackers` (
+ `custom_field_id` int(11) NOT NULL DEFAULT '0',
+ `tracker_id` int(11) NOT NULL DEFAULT '0',
+ UNIQUE KEY `index_custom_fields_trackers_on_custom_field_id_and_tracker_id` (`custom_field_id`,`tracker_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `custom_values`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `custom_values` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `customized_type` varchar(30) NOT NULL DEFAULT '',
+ `customized_id` int(11) NOT NULL DEFAULT '0',
+ `custom_field_id` int(11) NOT NULL DEFAULT '0',
+ `value` text,
+ PRIMARY KEY (`id`),
+ KEY `custom_values_customized` (`customized_type`,`customized_id`),
+ KEY `index_custom_values_on_custom_field_id` (`custom_field_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `data_exceptions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `data_exceptions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `message` varchar(255) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `delayed_jobs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `delayed_jobs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `priority` int(11) NOT NULL DEFAULT '0',
+ `attempts` int(11) NOT NULL DEFAULT '0',
+ `handler` text NOT NULL,
+ `last_error` text,
+ `run_at` datetime DEFAULT NULL,
+ `locked_at` datetime DEFAULT NULL,
+ `failed_at` datetime DEFAULT NULL,
+ `locked_by` varchar(255) DEFAULT NULL,
+ `queue` varchar(255) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `delayed_jobs_priority` (`priority`,`run_at`)
+) ENGINE=InnoDB AUTO_INCREMENT=13817 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `delayed_jobs_20161218`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `delayed_jobs_20161218` (
+ `id` int(11) NOT NULL DEFAULT '0',
+ `priority` int(11) NOT NULL DEFAULT '0',
+ `attempts` int(11) NOT NULL DEFAULT '0',
+ `handler` text NOT NULL,
+ `last_error` text,
+ `run_at` datetime DEFAULT NULL,
+ `locked_at` datetime DEFAULT NULL,
+ `failed_at` datetime DEFAULT NULL,
+ `locked_by` varchar(255) DEFAULT NULL,
+ `queue` varchar(255) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `department_members`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `department_members` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `department_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_department_members_on_department_id` (`department_id`),
+ KEY `index_department_members_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `departments`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `departments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `school_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `is_auth` tinyint(1) DEFAULT '0',
+ `identifier` varchar(255) DEFAULT NULL,
+ `host_count` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_departments_on_school_id` (`school_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=776 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `discipline_categories`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `discipline_categories` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `major_level` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `discuss_demos`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `discuss_demos` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `title` varchar(255) DEFAULT NULL,
+ `body` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `discusses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `discusses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `dis_type` varchar(255) DEFAULT NULL,
+ `dis_id` int(11) DEFAULT NULL,
+ `content` text,
+ `parent_id` int(11) DEFAULT NULL,
+ `root_id` int(11) DEFAULT NULL,
+ `praise_count` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `challenge_id` int(11) DEFAULT NULL,
+ `reward` int(11) DEFAULT NULL,
+ `hidden` tinyint(1) DEFAULT '0',
+ `last_reply_id` int(11) DEFAULT NULL,
+ `position` tinyint(4) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_discusses_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1820 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `documents`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `documents` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) NOT NULL DEFAULT '0',
+ `category_id` int(11) NOT NULL DEFAULT '0',
+ `title` varchar(60) NOT NULL DEFAULT '',
+ `description` text,
+ `created_on` datetime DEFAULT NULL,
+ `user_id` int(11) DEFAULT '0',
+ `is_public` int(11) DEFAULT '1',
+ PRIMARY KEY (`id`),
+ KEY `documents_project_id` (`project_id`),
+ KEY `index_documents_on_category_id` (`category_id`),
+ KEY `index_documents_on_created_on` (`created_on`)
+) ENGINE=InnoDB AUTO_INCREMENT=222 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `dts`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `dts` (
+ `Num` int(11) NOT NULL DEFAULT '0',
+ `Defect` varchar(50) DEFAULT NULL,
+ `Category` varchar(50) DEFAULT NULL,
+ `File` varchar(255) DEFAULT NULL,
+ `Method` varchar(255) DEFAULT NULL,
+ `Module` varchar(20) DEFAULT NULL,
+ `Variable` varchar(50) DEFAULT NULL,
+ `StartLine` int(11) DEFAULT NULL,
+ `IPLine` int(11) DEFAULT NULL,
+ `IPLineCode` varchar(200) DEFAULT NULL,
+ `Judge` varchar(15) DEFAULT NULL,
+ `Review` tinyint(4) DEFAULT NULL,
+ `Description` varchar(255) DEFAULT NULL,
+ `PreConditions` longtext,
+ `TraceInfo` longtext,
+ `Code` longtext,
+ `project_id` int(11) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `id` int(11) NOT NULL,
+ PRIMARY KEY (`Num`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `editor_of_documents`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `editor_of_documents` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `editor_id` int(11) DEFAULT NULL,
+ `org_document_comment_id` int(11) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1663 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `enabled_modules`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `enabled_modules` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `name` varchar(255) NOT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `enabled_modules_project_id` (`project_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=38150 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `enumerations`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `enumerations` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `position` int(11) DEFAULT '1',
+ `is_default` tinyint(1) NOT NULL DEFAULT '0',
+ `type` varchar(255) DEFAULT NULL,
+ `active` tinyint(1) NOT NULL DEFAULT '1',
+ `project_id` int(11) DEFAULT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `position_name` varchar(30) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_enumerations_on_project_id` (`project_id`),
+ KEY `index_enumerations_on_id_and_type` (`id`,`type`)
+) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `error_checks`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `error_checks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `game_indentifier` varchar(255) DEFAULT NULL,
+ `login` varchar(255) DEFAULT NULL,
+ `path` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=864 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `evaluate_records`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `evaluate_records` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ `game_id` int(11) DEFAULT NULL,
+ `consume_time` float DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `file_update` float DEFAULT NULL,
+ `git_pull` float DEFAULT NULL,
+ `create_pod` float DEFAULT NULL,
+ `pod_execute` float DEFAULT NULL,
+ `student_work` float DEFAULT NULL,
+ `test_cases` float DEFAULT NULL,
+ `retry` float DEFAULT NULL,
+ `game_build` float DEFAULT NULL,
+ `return_back` float DEFAULT NULL,
+ `brige` float DEFAULT NULL,
+ `create_status` datetime DEFAULT NULL,
+ `front_js` float DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `game` (`game_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1078311 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_answers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_answers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `exercise_question_id` int(11) DEFAULT NULL,
+ `exercise_choice_id` int(11) DEFAULT NULL,
+ `answer_text` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `score` int(11) DEFAULT '-1',
+ PRIMARY KEY (`id`),
+ KEY `eq` (`exercise_question_id`),
+ KEY `index_exercise_answers_on_exercise_choice_id` (`exercise_choice_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=393183 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_bank_choices`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_bank_choices` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `exercise_bank_question_id` int(11) DEFAULT NULL,
+ `choice_text` text,
+ `choice_position` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_bank_choices_on_exercise_bank_question_id` (`exercise_bank_question_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9343 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_bank_questions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_bank_questions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `question_title` text,
+ `question_type` int(11) DEFAULT NULL,
+ `question_number` int(11) DEFAULT NULL,
+ `question_score` int(11) DEFAULT NULL,
+ `exercise_bank_id` int(11) DEFAULT NULL,
+ `max_choices` int(11) DEFAULT NULL,
+ `min_choices` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ `is_necessary` int(11) DEFAULT '1',
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_bank_questions_on_exercise_bank_id` (`exercise_bank_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3007 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_bank_shixun_challenges`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_bank_shixun_challenges` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `challenge_id` int(11) DEFAULT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ `exercise_bank_question_id` int(11) DEFAULT NULL,
+ `question_score` int(11) DEFAULT NULL,
+ `position` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_bank_shixun_challenges_on_challenge_id` (`challenge_id`),
+ KEY `index_exercise_bank_shixun_challenges_on_shixun_id` (`shixun_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_bank_standard_answers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_bank_standard_answers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `exercise_bank_question_id` int(11) DEFAULT NULL,
+ `exercise_bank_choice_id` int(11) DEFAULT NULL,
+ `answer_text` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2330 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_banks`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_banks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `user_id` int(11) DEFAULT NULL,
+ `is_public` tinyint(1) DEFAULT NULL,
+ `quotes` int(11) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `course_list_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_banks_on_user_id` (`user_id`),
+ KEY `index_exercise_banks_on_course_list_id` (`course_list_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=403 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_choices`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_choices` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `exercise_question_id` int(11) DEFAULT NULL,
+ `choice_text` text,
+ `choice_position` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_choices_on_exercise_question_id` (`exercise_question_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23255 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_group_settings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_group_settings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `exercise_id` int(11) DEFAULT NULL,
+ `course_group_id` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `publish_time` datetime DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_group_settings_on_exercise_id` (`exercise_id`),
+ KEY `index_exercise_group_settings_on_course_group_id` (`course_group_id`),
+ KEY `index_exercise_group_settings_on_course_id` (`course_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_questions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_questions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `question_title` text,
+ `question_type` int(11) DEFAULT NULL,
+ `question_number` int(11) DEFAULT NULL,
+ `exercise_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `question_score` int(11) DEFAULT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_questions_on_exercise_id` (`exercise_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7396 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_shixun_answers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_shixun_answers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `exercise_question_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `exercise_shixun_challenge_id` int(11) DEFAULT NULL,
+ `answer_text` text,
+ `score` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_shixun_answers_on_exercise_question_id` (`exercise_question_id`),
+ KEY `index_exercise_shixun_answers_on_user_id` (`user_id`),
+ KEY `index_exercise_shixun_answers_on_exercise_shixun_challenge_id` (`exercise_shixun_challenge_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=663 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_shixun_challenges`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_shixun_challenges` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `challenge_id` int(11) DEFAULT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ `exercise_question_id` int(11) DEFAULT NULL,
+ `question_score` int(11) DEFAULT NULL,
+ `position` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_shixun_challenges_on_challenge_id` (`challenge_id`),
+ KEY `index_exercise_shixun_challenges_on_shixun_id` (`shixun_id`),
+ KEY `index_exercise_shixun_challenges_on_exercise_question_id` (`exercise_question_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=70 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_standard_answers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_standard_answers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `exercise_question_id` int(11) DEFAULT NULL,
+ `exercise_choice_id` int(11) DEFAULT NULL,
+ `answer_text` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_standard_answers_on_exercise_question_id` (`exercise_question_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8350 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercise_users`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercise_users` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `exercise_id` int(11) DEFAULT NULL,
+ `score` int(11) DEFAULT NULL,
+ `start_at` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `end_at` datetime DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `commit_status` int(11) DEFAULT '0',
+ `objective_score` int(11) DEFAULT '-1',
+ `subjective_score` int(11) DEFAULT '-1',
+ PRIMARY KEY (`id`),
+ KEY `index_exercise_users_on_exercise_id` (`exercise_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=81502 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `exercises`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `exercises` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `exercise_name` text,
+ `exercise_description` text,
+ `course_id` int(11) DEFAULT NULL,
+ `exercise_status` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `time` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `publish_time` datetime DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `show_result` int(11) DEFAULT NULL,
+ `question_random` int(11) DEFAULT '0',
+ `choice_random` int(11) DEFAULT '0',
+ `is_public` tinyint(1) DEFAULT '0',
+ `score_open` tinyint(1) DEFAULT '1',
+ `answer_open` tinyint(1) DEFAULT '1',
+ `exercise_bank_id` int(11) DEFAULT NULL,
+ `unified_setting` tinyint(1) DEFAULT '1',
+ `show_statistic` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=764 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `experiences`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `experiences` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `score` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `exper` (`container_id`,`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=217105 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `first_level_disciplines`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `first_level_disciplines` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `discipline_category_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_first_level_disciplines_on_discipline_category_id` (`discipline_category_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=312 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `first_pages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `first_pages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `web_title` varchar(255) DEFAULT NULL,
+ `title` varchar(255) DEFAULT NULL,
+ `description` text,
+ `page_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `sort_type` int(11) DEFAULT NULL,
+ `image_width` int(11) DEFAULT '107',
+ `image_height` int(11) DEFAULT '63',
+ `show_course` int(11) DEFAULT '1',
+ `show_contest` int(11) DEFAULT '1',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `forge_activities`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `forge_activities` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `forge_act_id` int(11) DEFAULT NULL,
+ `forge_act_type` varchar(255) DEFAULT NULL,
+ `org_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_forge_activities_on_forge_act_id` (`forge_act_id`),
+ KEY `forge_act_index` (`project_id`,`forge_act_id`,`created_at`,`forge_act_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=29856 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `forge_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `forge_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `forge_message_id` int(11) DEFAULT NULL,
+ `forge_message_type` varchar(255) DEFAULT NULL,
+ `viewed` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `secret_key` varchar(255) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `operate_user_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_forge_messages_on_user_id_and_project_id_and_created_at` (`user_id`,`project_id`,`created_at`),
+ KEY `index_forge_messages_on_forge_message_id_and_forge_message_type` (`forge_message_id`,`forge_message_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=27628 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `forums`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `forums` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `description` text,
+ `topic_count` int(11) DEFAULT '0',
+ `memo_count` int(11) DEFAULT '0',
+ `last_memo_id` int(11) DEFAULT '0',
+ `creator_id` int(11) NOT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `sticky` int(11) DEFAULT NULL,
+ `locked` int(11) DEFAULT NULL,
+ `visits` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `forwards`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `forwards` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `from_id` int(11) DEFAULT NULL,
+ `from_type` varchar(255) DEFAULT NULL,
+ `to_id` int(11) DEFAULT NULL,
+ `to_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=603 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `game_codes`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `game_codes` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `game_id` int(11) DEFAULT NULL,
+ `original_code` longtext,
+ `new_code` longtext,
+ `path` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `current_code` longtext,
+ PRIMARY KEY (`id`),
+ KEY `game_id` (`game_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=284340 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `game_outputs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `game_outputs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `code` int(11) DEFAULT NULL,
+ `game_id` int(11) DEFAULT NULL,
+ `msg` text,
+ `out_put` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `games`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `games` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `myshixun_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `status` int(11) DEFAULT '0',
+ `final_score` int(11) DEFAULT '0',
+ `challenge_id` int(11) DEFAULT NULL,
+ `open_time` datetime DEFAULT NULL,
+ `identifier` varchar(255) DEFAULT NULL,
+ `answer_open` tinyint(1) DEFAULT '0',
+ `end_time` datetime DEFAULT NULL,
+ `retry_status` int(11) DEFAULT '0',
+ `resubmit_identifier` varchar(255) DEFAULT NULL,
+ `test_sets_view` tinyint(1) DEFAULT '0',
+ `picture_path` text,
+ `accuracy` float DEFAULT NULL,
+ `modify_time` datetime DEFAULT NULL,
+ `star` int(11) DEFAULT '0',
+ `cost_time` int(11) DEFAULT '0',
+ `evaluate_count` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `user` (`user_id`),
+ KEY `challenge` (`challenge_id`),
+ KEY `myshixun` (`myshixun_id`),
+ KEY `index_games_on_identifier` (`identifier`)
+) ENGINE=InnoDB AUTO_INCREMENT=292926 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `gitlab_urls`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `gitlab_urls` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `myshixun_id` int(11) DEFAULT NULL,
+ `url` varchar(255) DEFAULT NULL,
+ `myshixun_identifier` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=16162 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `grades`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `grades` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `score` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `container` (`user_id`,`container_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=332272 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `groups_users`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `groups_users` (
+ `group_id` int(11) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ UNIQUE KEY `groups_users_ids` (`group_id`,`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `helps`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `helps` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `about_us` text,
+ `agreement` text,
+ `status` text,
+ `help_center` text,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homepages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homepages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `article_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_homepages_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23796 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_attaches`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_attaches` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `bid_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `reward` varchar(255) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `state` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT '0',
+ `score` float DEFAULT '0',
+ `is_teacher_score` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_homework_attaches_on_bid_id` (`bid_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24754 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_bank_samples`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_bank_samples` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `input` text,
+ `output` text,
+ `homework_bank_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_homework_bank_samples_on_homework_bank_id` (`homework_bank_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=197 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_bank_shixuns`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_bank_shixuns` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `homework_bank_id` int(11) DEFAULT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_homework_bank_shixuns_on_homework_bank_id` (`homework_bank_id`),
+ KEY `index_homework_bank_shixuns_on_shixun_id` (`shixun_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_bank_tests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_bank_tests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `input` text,
+ `output` text,
+ `test_type` tinyint(1) DEFAULT NULL,
+ `homework_bank_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=40569 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_banks`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_banks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `homework_type` int(11) DEFAULT NULL,
+ `quotes` int(11) DEFAULT '0',
+ `is_public` tinyint(1) DEFAULT NULL,
+ `language` varchar(255) DEFAULT NULL,
+ `standard_code` longtext,
+ `min_num` int(11) DEFAULT NULL,
+ `max_num` int(11) DEFAULT NULL,
+ `base_on_project` tinyint(1) DEFAULT NULL,
+ `applicable_syllabus` varchar(255) DEFAULT NULL,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `reference_answer` text,
+ `syllabus_id` int(11) DEFAULT NULL,
+ `major_level` text,
+ `discipline_category_id` text,
+ `first_level_discipline_id` text,
+ `course_list_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4233 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_challenge_settings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_challenge_settings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `challenge_id` int(11) DEFAULT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ `score` float DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_homework_challenge_settings_on_homework_common_id` (`homework_common_id`),
+ KEY `index_homework_challenge_settings_on_challenge_id` (`challenge_id`),
+ KEY `index_homework_challenge_settings_on_shixun_id` (`shixun_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5414 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_commons`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_commons` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `description` text,
+ `publish_time` datetime DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `homework_type` int(11) DEFAULT '1',
+ `late_penalty` varchar(255) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `teacher_priority` int(11) DEFAULT '1',
+ `anonymous_comment` int(11) DEFAULT '0',
+ `quotes` int(11) DEFAULT '0',
+ `is_open` int(11) DEFAULT '0',
+ `simi_time` datetime DEFAULT NULL,
+ `score_open` int(11) DEFAULT '1',
+ `anonymous_appeal` int(11) DEFAULT '0',
+ `homework_bank_id` int(11) DEFAULT NULL,
+ `is_update` tinyint(1) DEFAULT '0',
+ `is_public` tinyint(1) DEFAULT '0',
+ `reference_answer` text,
+ `answer_public` tinyint(1) DEFAULT '1',
+ `archive_time` datetime DEFAULT NULL,
+ `allow_late` tinyint(1) DEFAULT '1',
+ `late_time` datetime DEFAULT NULL,
+ `work_public` tinyint(1) DEFAULT '1',
+ `explanation` text,
+ `unified_setting` tinyint(1) DEFAULT '1',
+ PRIMARY KEY (`id`),
+ KEY `index_homework_commons_on_course_id_and_id` (`course_id`,`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7704 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_commons_shixuns`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_commons_shixuns` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1589 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_detail_groups`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_detail_groups` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `min_num` int(11) DEFAULT NULL,
+ `max_num` int(11) DEFAULT NULL,
+ `base_on_project` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_homework_detail_groups_on_homework_common_id` (`homework_common_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=321 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_detail_manuals`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_detail_manuals` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `ta_proportion` float DEFAULT NULL,
+ `comment_status` int(11) DEFAULT NULL,
+ `evaluation_start` datetime DEFAULT NULL,
+ `evaluation_end` datetime DEFAULT NULL,
+ `evaluation_num` int(11) DEFAULT NULL,
+ `absence_penalty` int(11) DEFAULT '1',
+ `homework_common_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `no_anon_penalty` int(11) DEFAULT '1',
+ `appeal_penalty` int(11) DEFAULT '0',
+ `ta_mode` int(11) DEFAULT '1',
+ `appeal_time` datetime DEFAULT NULL,
+ `te_proportion` float DEFAULT '1',
+ `final_mode` tinyint(1) DEFAULT '0',
+ `answer_open_evaluation` tinyint(1) DEFAULT '0',
+ `shixun_evaluation` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7700 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_detail_programings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_detail_programings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `language` varchar(255) DEFAULT NULL,
+ `standard_code` longtext,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `ta_proportion` float DEFAULT '0.1',
+ `question_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2766 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_evaluations`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_evaluations` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` varchar(255) DEFAULT NULL,
+ `homework_attach_id` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=17792 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_for_courses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_for_courses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `course_id` int(11) DEFAULT NULL,
+ `bid_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_homework_for_courses_on_course_id` (`course_id`),
+ KEY `index_homework_for_courses_on_bid_id` (`bid_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=737 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_group_settings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_group_settings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `course_group_id` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `publish_time` datetime DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_homework_group_settings_on_homework_common_id` (`homework_common_id`),
+ KEY `index_homework_group_settings_on_course_group_id` (`course_group_id`),
+ KEY `index_homework_group_settings_on_course_id` (`course_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=252 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_reference_answers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_reference_answers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `answer` text,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_homework_reference_answers_on_homework_common_id` (`homework_common_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_samples`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_samples` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `input` text,
+ `output` text,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_homework_samples_on_homework_common_id` (`homework_common_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1212 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_tests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_tests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `input` text,
+ `output` text,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `result` int(11) DEFAULT NULL,
+ `error_msg` text,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=51640 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `homework_users`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `homework_users` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `homework_attach_id` varchar(255) DEFAULT NULL,
+ `user_id` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `import_course_users`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `import_course_users` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_student_id` varchar(255) DEFAULT NULL,
+ `user_name` varchar(255) DEFAULT NULL,
+ `user_group_name` varchar(255) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `school_id` int(11) DEFAULT NULL,
+ `status` tinyint(1) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_import_course_users_on_course_id` (`course_id`),
+ KEY `index_import_course_users_on_school_id` (`school_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `import_students`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `import_students` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `student_number` varchar(255) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `school_id` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `informs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `informs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `innodb_monitor`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `innodb_monitor` (
+ `a` int(11) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `invite_lists`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `invite_lists` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `mail` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=141 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `issue_categories`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `issue_categories` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) NOT NULL DEFAULT '0',
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `assigned_to_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `issue_categories_project_id` (`project_id`),
+ KEY `index_issue_categories_on_assigned_to_id` (`assigned_to_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `issue_relations`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `issue_relations` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `issue_from_id` int(11) NOT NULL,
+ `issue_to_id` int(11) NOT NULL,
+ `relation_type` varchar(255) NOT NULL DEFAULT '',
+ `delay` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `index_issue_relations_on_issue_from_id_and_issue_to_id` (`issue_from_id`,`issue_to_id`),
+ KEY `index_issue_relations_on_issue_from_id` (`issue_from_id`),
+ KEY `index_issue_relations_on_issue_to_id` (`issue_to_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `issue_statuses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `issue_statuses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `is_closed` tinyint(1) NOT NULL DEFAULT '0',
+ `is_default` tinyint(1) NOT NULL DEFAULT '0',
+ `position` int(11) DEFAULT '1',
+ `default_done_ratio` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_issue_statuses_on_position` (`position`),
+ KEY `index_issue_statuses_on_is_closed` (`is_closed`),
+ KEY `index_issue_statuses_on_is_default` (`is_default`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `issues`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `issues` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `tracker_id` int(11) NOT NULL,
+ `project_id` int(11) NOT NULL,
+ `subject` varchar(255) NOT NULL DEFAULT '',
+ `description` text,
+ `due_date` date DEFAULT NULL,
+ `category_id` int(11) DEFAULT NULL,
+ `status_id` int(11) NOT NULL,
+ `assigned_to_id` int(11) DEFAULT NULL,
+ `priority_id` int(11) NOT NULL,
+ `fixed_version_id` int(11) DEFAULT NULL,
+ `author_id` int(11) NOT NULL,
+ `lock_version` int(11) NOT NULL DEFAULT '0',
+ `created_on` datetime DEFAULT NULL,
+ `updated_on` datetime DEFAULT NULL,
+ `start_date` date DEFAULT NULL,
+ `done_ratio` int(11) NOT NULL DEFAULT '0',
+ `estimated_hours` float DEFAULT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `root_id` int(11) DEFAULT NULL,
+ `lft` int(11) DEFAULT NULL,
+ `rgt` int(11) DEFAULT NULL,
+ `is_private` tinyint(1) NOT NULL DEFAULT '0',
+ `closed_on` datetime DEFAULT NULL,
+ `project_issues_index` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `issues_project_id` (`project_id`),
+ KEY `index_issues_on_status_id` (`status_id`),
+ KEY `index_issues_on_category_id` (`category_id`),
+ KEY `index_issues_on_assigned_to_id` (`assigned_to_id`),
+ KEY `index_issues_on_fixed_version_id` (`fixed_version_id`),
+ KEY `index_issues_on_tracker_id` (`tracker_id`),
+ KEY `index_issues_on_priority_id` (`priority_id`),
+ KEY `index_issues_on_author_id` (`author_id`),
+ KEY `index_issues_on_created_on` (`created_on`),
+ KEY `index_issues_on_root_id_and_lft_and_rgt` (`root_id`,`lft`,`rgt`)
+) ENGINE=InnoDB AUTO_INCREMENT=11837 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `join_in_competitions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `join_in_competitions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `competition_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `join_in_contests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `join_in_contests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `bid_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `journal_details`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `journal_details` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `journal_id` int(11) NOT NULL DEFAULT '0',
+ `property` varchar(30) NOT NULL DEFAULT '',
+ `prop_key` varchar(30) NOT NULL DEFAULT '',
+ `old_value` text,
+ `value` text,
+ PRIMARY KEY (`id`),
+ KEY `journal_details_journal_id` (`journal_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24527 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `journal_replies`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `journal_replies` (
+ `journal_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `reply_id` int(11) DEFAULT NULL,
+ KEY `index_journal_replies_on_user_id` (`user_id`),
+ KEY `index_journal_replies_on_journal_id` (`journal_id`),
+ KEY `index_journal_replies_on_reply_id` (`reply_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `journals`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `journals` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `journalized_id` int(11) NOT NULL DEFAULT '0',
+ `journalized_type` varchar(30) NOT NULL DEFAULT '',
+ `user_id` int(11) NOT NULL DEFAULT '0',
+ `notes` text,
+ `created_on` datetime NOT NULL,
+ `private_notes` tinyint(1) NOT NULL DEFAULT '0',
+ `parent_id` int(11) DEFAULT NULL,
+ `comments_count` int(11) DEFAULT '0',
+ `reply_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `journals_journalized_id` (`journalized_id`,`journalized_type`),
+ KEY `index_journals_on_user_id` (`user_id`),
+ KEY `index_journals_on_journalized_id` (`journalized_id`),
+ KEY `index_journals_on_created_on` (`created_on`)
+) ENGINE=InnoDB AUTO_INCREMENT=19600 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `journals_for_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `journals_for_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `jour_id` int(11) DEFAULT NULL,
+ `jour_type` varchar(255) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `notes` text,
+ `status` int(11) DEFAULT NULL,
+ `reply_id` int(11) DEFAULT NULL,
+ `created_on` datetime NOT NULL,
+ `updated_on` datetime NOT NULL,
+ `m_parent_id` varchar(255) DEFAULT NULL,
+ `is_readed` tinyint(1) DEFAULT NULL,
+ `m_reply_count` int(11) DEFAULT NULL,
+ `m_reply_id` int(11) DEFAULT NULL,
+ `is_comprehensive_evaluation` int(11) DEFAULT NULL,
+ `private` int(11) DEFAULT '0',
+ `root_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_journals_for_messages_on_root_id` (`root_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=80534 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `kindeditor_assets`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `kindeditor_assets` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `asset` varchar(255) DEFAULT NULL,
+ `file_size` int(11) DEFAULT NULL,
+ `file_type` varchar(255) DEFAULT NULL,
+ `owner_id` int(11) DEFAULT NULL,
+ `asset_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `owner_type` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24663 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `major_courses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `major_courses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `course_list_id` int(11) DEFAULT NULL,
+ `major_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_major_courses_on_course_list_id` (`course_list_id`),
+ KEY `index_major_courses_on_major_id` (`major_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `majors`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `majors` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `major_code` varchar(255) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `first_level_discipline_id` int(11) DEFAULT NULL,
+ `discipline_category_id` int(11) DEFAULT NULL,
+ `major_level` int(11) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ `updated_at` datetime DEFAULT NULL,
+ `support_shixuns` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_majors_on_first_level_discipline_id` (`first_level_discipline_id`),
+ KEY `index_majors_on_discipline_category_id` (`discipline_category_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1660 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `mark_downs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mark_downs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `description` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `member_roles`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `member_roles` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `member_id` int(11) NOT NULL,
+ `role_id` int(11) NOT NULL,
+ `inherited_from` int(11) DEFAULT NULL,
+ `is_current` int(11) DEFAULT '1',
+ PRIMARY KEY (`id`),
+ KEY `index_member_roles_on_member_id` (`member_id`),
+ KEY `index_member_roles_on_role_id` (`role_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=66130 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `members`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `members` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL DEFAULT '0',
+ `project_id` int(11) DEFAULT '0',
+ `created_on` datetime DEFAULT NULL,
+ `mail_notification` tinyint(1) NOT NULL DEFAULT '0',
+ `course_id` int(11) DEFAULT '-1',
+ `course_group_id` int(11) DEFAULT '0',
+ `is_collect` int(11) DEFAULT '1',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `index_members_on_user_id_and_project_id` (`user_id`,`project_id`,`course_id`),
+ KEY `index_members_on_user_id` (`user_id`),
+ KEY `index_members_on_project_id` (`project_id`),
+ KEY `index_members_on_course_id` (`course_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=64562 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `memo_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `memo_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `forum_id` int(11) DEFAULT NULL,
+ `memo_id` int(11) DEFAULT NULL,
+ `memo_type` varchar(255) DEFAULT NULL,
+ `viewed` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_memo_messages_on_user_id_and_forum_id_and_created_at` (`user_id`,`forum_id`,`created_at`),
+ KEY `index_memo_messages_on_memo_id_and_memo_type` (`memo_id`,`memo_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=3891 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `memo_tag_repertoires`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `memo_tag_repertoires` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `memo_id` int(11) DEFAULT NULL,
+ `tag_repertoire_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_memo_tag_repertoires_on_memo_id` (`memo_id`),
+ KEY `index_memo_tag_repertoires_on_tag_repertoire_id` (`tag_repertoire_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=184 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `memos`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `memos` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `forum_id` int(11) NOT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `subject` varchar(255) NOT NULL,
+ `content` longtext NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `replies_count` int(11) DEFAULT '0',
+ `last_reply_id` int(11) DEFAULT NULL,
+ `lock` tinyint(1) DEFAULT '0',
+ `sticky` tinyint(1) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `viewed_count` int(11) DEFAULT '0',
+ `root_id` int(11) DEFAULT NULL,
+ `reward` int(11) DEFAULT NULL,
+ `language` varchar(255) DEFAULT NULL,
+ `hidden` tinyint(4) DEFAULT '0',
+ `repertoire_name` varchar(255) DEFAULT NULL,
+ `is_md` tinyint(1) DEFAULT '1',
+ `all_replies_count` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_memos_on_root_id` (`root_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3377 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `message_alls`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `message_alls` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `message_id` int(11) DEFAULT NULL,
+ `message_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_message_alls_on_user_id_and_message_id_and_created_at` (`user_id`,`message_id`,`created_at`),
+ KEY `index_message_alls_on_message_type` (`message_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=974095 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `board_id` int(11) NOT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `subject` varchar(255) NOT NULL DEFAULT '',
+ `content` longtext,
+ `author_id` int(11) DEFAULT NULL,
+ `replies_count` int(11) NOT NULL DEFAULT '0',
+ `last_reply_id` int(11) DEFAULT NULL,
+ `created_on` datetime NOT NULL,
+ `updated_on` datetime NOT NULL,
+ `locked` tinyint(1) DEFAULT '0',
+ `sticky` int(11) DEFAULT '0',
+ `reply_id` int(11) DEFAULT NULL,
+ `quotes` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT '0',
+ `root_id` int(11) DEFAULT NULL,
+ `visits` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `messages_board_id` (`board_id`),
+ KEY `messages_parent_id` (`parent_id`),
+ KEY `index_messages_on_last_reply_id` (`last_reply_id`),
+ KEY `index_messages_on_author_id` (`author_id`),
+ KEY `index_messages_on_created_on` (`created_on`),
+ KEY `index_messages_on_root_id` (`root_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=34681 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `mirror_migrate_errors`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mirror_migrate_errors` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `game_info` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `mirror_operation_records`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mirror_operation_records` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `mirror_repository_id` int(11) DEFAULT NULL,
+ `mirror_id` text,
+ `mirror_name` text,
+ `status` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `old_tag` varchar(255) DEFAULT NULL,
+ `new_tag` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=561 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `mirror_repositories`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mirror_repositories` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `mirrorID` varchar(255) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `main_type` varchar(255) DEFAULT NULL,
+ `description` text,
+ `status` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `type_name` text,
+ `script_template` longtext,
+ `resource_limit` int(11) DEFAULT '10000',
+ `memory_limit` int(11) DEFAULT '1024',
+ `cpu_limit` tinyint(4) DEFAULT '1',
+ `time_limit` int(11) DEFAULT '120',
+ `should_compile` tinyint(1) DEFAULT NULL,
+ `repertoire_id` int(11) DEFAULT NULL,
+ `sub_repertoire_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=109 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `mirror_repository_types`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mirror_repository_types` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `mirror_type_id` int(11) DEFAULT NULL,
+ `mirror_repository_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_mirror_repository_types_on_mirror_type_id` (`mirror_type_id`),
+ KEY `index_mirror_repository_types_on_mirror_repository_id` (`mirror_repository_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `mirror_scripts`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mirror_scripts` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `mirror_repository_id` int(11) DEFAULT NULL,
+ `script` longtext,
+ `script_type` varchar(255) DEFAULT NULL,
+ `description` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `mirror_types`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mirror_types` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `mirror_update_records`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mirror_update_records` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `mirror_repository_id` int(11) DEFAULT NULL,
+ `oldName` varchar(255) DEFAULT NULL,
+ `newName` varchar(255) DEFAULT NULL,
+ `oldType` varchar(255) DEFAULT NULL,
+ `newType` varchar(255) DEFAULT NULL,
+ `oldTag` text,
+ `newTag` text,
+ `oldDescription` text,
+ `newDescription` text,
+ `oldStatus` int(11) DEFAULT NULL,
+ `newStatus` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_mirror_update_records_on_user_id` (`user_id`),
+ KEY `index_mirror_update_records_on_mirror_repository_id` (`mirror_repository_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `mul_tests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `mul_tests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `action` varchar(255) DEFAULT NULL,
+ `time` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `language` varchar(255) DEFAULT NULL,
+ `num` int(11) DEFAULT NULL,
+ `indentifier` varchar(255) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `myshixun_members`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `myshixun_members` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `myshixun_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `role` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1434 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `myshixuns`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `myshixuns` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `is_public` tinyint(1) DEFAULT '1',
+ `user_id` int(11) DEFAULT NULL,
+ `gpid` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `status` int(11) DEFAULT '0',
+ `identifier` varchar(255) DEFAULT NULL,
+ `commit_id` varchar(255) DEFAULT NULL,
+ `modify_time` datetime DEFAULT NULL,
+ `reset_time` datetime DEFAULT NULL,
+ `system_tip` tinyint(1) DEFAULT '0',
+ `git_url` varchar(255) DEFAULT NULL,
+ `onclick_time` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `index_myshixuns_on_user_id_and_shixun_id` (`user_id`,`shixun_id`),
+ KEY `index_myshixuns_on_identifier` (`identifier`),
+ KEY `index_myshixuns_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=86406 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `news`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `news` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `title` varchar(60) NOT NULL DEFAULT '',
+ `summary` varchar(255) DEFAULT '',
+ `description` text,
+ `author_id` int(11) NOT NULL DEFAULT '0',
+ `created_on` datetime DEFAULT NULL,
+ `comments_count` int(11) NOT NULL DEFAULT '0',
+ `course_id` int(11) DEFAULT NULL,
+ `sticky` int(11) DEFAULT '0',
+ `org_subfield_id` int(11) DEFAULT NULL,
+ `contest_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `news_project_id` (`project_id`),
+ KEY `index_news_on_author_id` (`author_id`),
+ KEY `index_news_on_created_on` (`created_on`)
+) ENGINE=InnoDB AUTO_INCREMENT=2585 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `no_uses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `no_uses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL,
+ `no_use_type` varchar(255) DEFAULT NULL,
+ `no_use_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `notificationcomments`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `notificationcomments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `notificationcommented_type` varchar(255) DEFAULT NULL,
+ `notificationcommented_id` int(11) DEFAULT NULL,
+ `author_id` int(11) DEFAULT NULL,
+ `notificationcomments` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `onclick_times`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `onclick_times` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `onclick_time` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=28992 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `open_id_authentication_associations`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `open_id_authentication_associations` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `issued` int(11) DEFAULT NULL,
+ `lifetime` int(11) DEFAULT NULL,
+ `handle` varchar(255) DEFAULT NULL,
+ `assoc_type` varchar(255) DEFAULT NULL,
+ `server_url` blob,
+ `secret` blob,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `open_id_authentication_nonces`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `open_id_authentication_nonces` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `timestamp` int(11) NOT NULL,
+ `server_url` varchar(255) DEFAULT NULL,
+ `salt` varchar(255) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `open_source_projects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `open_source_projects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `commit_count` int(11) DEFAULT '0',
+ `code_line` int(11) DEFAULT '0',
+ `users_count` int(11) DEFAULT '0',
+ `last_commit_time` date DEFAULT NULL,
+ `url` varchar(255) DEFAULT NULL,
+ `date_collected` date DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=488570 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `option_numbers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `option_numbers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `memo` int(11) DEFAULT NULL,
+ `messages_for_issues` int(11) DEFAULT NULL,
+ `issues_status` int(11) DEFAULT NULL,
+ `replay_for_message` int(11) DEFAULT NULL,
+ `replay_for_memo` int(11) DEFAULT NULL,
+ `follow` int(11) DEFAULT NULL,
+ `tread` int(11) DEFAULT NULL,
+ `praise_by_one` int(11) DEFAULT NULL,
+ `praise_by_two` int(11) DEFAULT NULL,
+ `praise_by_three` int(11) DEFAULT NULL,
+ `tread_by_one` int(11) DEFAULT NULL,
+ `tread_by_two` int(11) DEFAULT NULL,
+ `tread_by_three` int(11) DEFAULT NULL,
+ `changeset` int(11) DEFAULT NULL,
+ `document` int(11) DEFAULT NULL,
+ `attachment` int(11) DEFAULT NULL,
+ `issue_done_ratio` int(11) DEFAULT NULL,
+ `post_issue` int(11) DEFAULT NULL,
+ `score_type` int(11) DEFAULT NULL,
+ `total_score` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=22597 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `org_activities`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `org_activities` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `org_act_id` int(11) DEFAULT NULL,
+ `org_act_type` varchar(255) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=58588 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `org_courses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `org_courses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `organization_id` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=98 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `org_document_comments`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `org_document_comments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `title` text,
+ `content` text,
+ `organization_id` int(11) DEFAULT NULL,
+ `creator_id` int(11) DEFAULT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `reply_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `locked` tinyint(1) DEFAULT '0',
+ `sticky` int(11) DEFAULT '0',
+ `org_subfield_id` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT '0',
+ `root_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_org_document_comments_on_root_id` (`root_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=6511 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `org_member_roles`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `org_member_roles` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `org_member_id` int(11) DEFAULT NULL,
+ `role_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=634 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `org_members`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `org_members` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `organization_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=637 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `org_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `org_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `sender_id` int(11) DEFAULT NULL,
+ `organization_id` int(11) DEFAULT NULL,
+ `message_type` varchar(255) DEFAULT NULL,
+ `message_id` int(11) DEFAULT NULL,
+ `viewed` int(11) DEFAULT NULL,
+ `content` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `status` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=233 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `org_projects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `org_projects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `organization_id` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=138 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `org_subfield_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `org_subfield_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `org_subfield_id` int(11) DEFAULT NULL,
+ `message_id` int(11) DEFAULT NULL,
+ `message_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `org_subfields`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `org_subfields` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `organization_id` int(11) DEFAULT NULL,
+ `priority` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `field_type` varchar(255) DEFAULT NULL,
+ `hide` int(11) DEFAULT '0',
+ `status` int(11) DEFAULT '1',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=717 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `organizations`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `organizations` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `creator_id` int(11) DEFAULT NULL,
+ `home_id` int(11) DEFAULT NULL,
+ `is_public` tinyint(1) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `allow_guest_download` tinyint(1) DEFAULT '1',
+ `visits` int(11) DEFAULT '0',
+ `show_mode` int(11) DEFAULT '0',
+ `allow_teacher` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=150 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `phone_app_versions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `phone_app_versions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `version` varchar(255) DEFAULT NULL,
+ `description` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `platform_samples`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `platform_samples` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `samples_type` varchar(255) DEFAULT NULL,
+ `contents` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `poll_answers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `poll_answers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `poll_question_id` int(11) DEFAULT NULL,
+ `answer_text` text,
+ `answer_position` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3961 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `poll_group_settings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `poll_group_settings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `poll_id` int(11) DEFAULT NULL,
+ `course_group_id` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `publish_time` datetime DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_poll_group_settings_on_poll_id` (`poll_id`),
+ KEY `index_poll_group_settings_on_course_group_id` (`course_group_id`),
+ KEY `index_poll_group_settings_on_course_id` (`course_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `poll_questions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `poll_questions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `question_title` varchar(255) DEFAULT NULL,
+ `question_type` int(11) DEFAULT NULL,
+ `is_necessary` int(11) DEFAULT NULL,
+ `poll_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `question_number` int(11) DEFAULT NULL,
+ `max_choices` int(11) DEFAULT '0',
+ `min_choices` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1391 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `poll_users`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `poll_users` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `poll_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `commit_status` int(11) DEFAULT '0',
+ `start_at` datetime DEFAULT NULL,
+ `end_at` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=35650 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `poll_votes`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `poll_votes` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `poll_question_id` int(11) DEFAULT NULL,
+ `poll_answer_id` int(11) DEFAULT NULL,
+ `vote_text` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=41374 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `polls`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `polls` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `polls_name` varchar(255) DEFAULT NULL,
+ `polls_type` varchar(255) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `polls_status` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `publish_time` datetime DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `polls_description` text,
+ `show_result` int(11) DEFAULT '1',
+ `exercise_bank_id` int(11) DEFAULT NULL,
+ `is_public` tinyint(1) DEFAULT '0',
+ `unified_setting` tinyint(1) DEFAULT '1',
+ PRIMARY KEY (`id`),
+ KEY `index_polls_on_course_id` (`course_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=492 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `praise_tread_caches`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `praise_tread_caches` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `object_id` int(11) NOT NULL,
+ `object_type` varchar(255) DEFAULT NULL,
+ `praise_num` int(11) DEFAULT NULL,
+ `tread_num` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9870 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `praise_treads`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `praise_treads` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL,
+ `praise_tread_object_id` int(11) DEFAULT NULL,
+ `praise_tread_object_type` varchar(255) DEFAULT NULL,
+ `praise_or_tread` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `praise_tread` (`praise_tread_object_id`,`praise_tread_object_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=31598 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `principal_activities`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `principal_activities` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `principal_id` int(11) DEFAULT NULL,
+ `principal_act_id` int(11) DEFAULT NULL,
+ `principal_act_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=69540 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `private_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `private_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `target_id` int(11) DEFAULT NULL,
+ `sender_id` int(11) DEFAULT NULL,
+ `receiver_id` int(11) DEFAULT NULL,
+ `content` text,
+ `send_time` datetime DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_private_messages_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=177 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `professional_levels`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `professional_levels` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `level` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `project_infos`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `project_infos` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4934 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `project_scores`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `project_scores` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` varchar(255) DEFAULT NULL,
+ `score` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `issue_num` int(11) DEFAULT '0',
+ `issue_journal_num` int(11) DEFAULT '0',
+ `news_num` int(11) DEFAULT '0',
+ `documents_num` int(11) DEFAULT '0',
+ `changeset_num` int(11) DEFAULT '0',
+ `board_message_num` int(11) DEFAULT '0',
+ `board_num` int(11) DEFAULT '0',
+ `attach_num` int(11) DEFAULT '0',
+ `commit_time` datetime DEFAULT NULL,
+ `pull_request_num` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3100 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `project_statuses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `project_statuses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `changesets_count` int(11) DEFAULT NULL,
+ `watchers_count` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `project_type` int(11) DEFAULT NULL,
+ `grade` float DEFAULT '0',
+ `course_ac_para` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_project_statuses_on_grade` (`grade`)
+) ENGINE=InnoDB AUTO_INCREMENT=3141 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `projecting_softapplictions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `projecting_softapplictions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `softapplication_id` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `projects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `projects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `description` text,
+ `homepage` varchar(255) DEFAULT '',
+ `is_public` tinyint(1) NOT NULL DEFAULT '1',
+ `parent_id` int(11) DEFAULT NULL,
+ `created_on` datetime DEFAULT NULL,
+ `updated_on` datetime DEFAULT NULL,
+ `identifier` varchar(255) DEFAULT NULL,
+ `status` int(11) NOT NULL DEFAULT '1',
+ `lft` int(11) DEFAULT NULL,
+ `rgt` int(11) DEFAULT NULL,
+ `inherit_members` tinyint(1) NOT NULL DEFAULT '0',
+ `project_type` int(11) DEFAULT NULL,
+ `hidden_repo` tinyint(1) NOT NULL DEFAULT '0',
+ `attachmenttype` int(11) DEFAULT '1',
+ `user_id` int(11) DEFAULT NULL,
+ `dts_test` int(11) DEFAULT '0',
+ `enterprise_name` varchar(255) DEFAULT NULL,
+ `organization_id` int(11) DEFAULT NULL,
+ `project_new_type` int(11) DEFAULT NULL,
+ `gpid` int(11) DEFAULT NULL,
+ `forked_from_project_id` int(11) DEFAULT NULL,
+ `forked_count` int(11) DEFAULT NULL,
+ `publish_resource` int(11) DEFAULT '0',
+ `visits` int(11) DEFAULT '0',
+ `hot` int(11) DEFAULT '0',
+ `invite_code` varchar(255) DEFAULT NULL,
+ `qrcode` varchar(255) DEFAULT NULL,
+ `qrcode_expiretime` int(11) DEFAULT '0',
+ `script` text,
+ `training_status` tinyint(4) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_projects_on_lft` (`lft`),
+ KEY `index_projects_on_rgt` (`rgt`)
+) ENGINE=InnoDB AUTO_INCREMENT=3215 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `projects_trackers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `projects_trackers` (
+ `project_id` int(11) NOT NULL DEFAULT '0',
+ `tracker_id` int(11) NOT NULL DEFAULT '0',
+ UNIQUE KEY `projects_trackers_unique` (`project_id`,`tracker_id`),
+ KEY `projects_trackers_project_id` (`project_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `pull_requests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `pull_requests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `pull_request_id` int(11) DEFAULT NULL,
+ `gpid` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `status` int(11) DEFAULT '0',
+ `project_id` int(11) DEFAULT NULL,
+ `title` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=312 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `quality_analyses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `quality_analyses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `author_login` varchar(255) DEFAULT NULL,
+ `rep_identifier` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `sonar_version` int(11) DEFAULT '0',
+ `path` varchar(255) DEFAULT NULL,
+ `branch` varchar(255) DEFAULT NULL,
+ `language` varchar(255) DEFAULT NULL,
+ `sonar_name` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=237 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `queries`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `queries` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `filters` text,
+ `user_id` int(11) NOT NULL DEFAULT '0',
+ `is_public` tinyint(1) NOT NULL DEFAULT '0',
+ `column_names` text,
+ `sort_criteria` text,
+ `group_by` varchar(255) DEFAULT NULL,
+ `type` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_queries_on_project_id` (`project_id`),
+ KEY `index_queries_on_user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `question_banks`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `question_banks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` text,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `quotes` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `is_public` tinyint(1) DEFAULT NULL,
+ `course_list_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2490 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `reference_materials`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `reference_materials` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `book` varchar(255) DEFAULT NULL,
+ `editor` varchar(255) DEFAULT NULL,
+ `press` varchar(255) DEFAULT NULL,
+ `syllabus_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_reference_materials_on_syllabus_id` (`syllabus_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `relative_memo_to_open_source_projects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `relative_memo_to_open_source_projects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `osp_id` int(11) DEFAULT NULL,
+ `relative_memo_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=596992 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `relative_memos`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `relative_memos` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `osp_id` int(11) DEFAULT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `subject` varchar(255) NOT NULL,
+ `content` mediumtext NOT NULL,
+ `author_id` int(11) DEFAULT NULL,
+ `replies_count` int(11) DEFAULT '0',
+ `last_reply_id` int(11) DEFAULT NULL,
+ `lock` tinyint(1) DEFAULT '0',
+ `sticky` tinyint(1) DEFAULT '0',
+ `is_quote` tinyint(1) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `viewed_count_crawl` int(11) DEFAULT '0',
+ `viewed_count_local` int(11) DEFAULT '0',
+ `url` varchar(255) DEFAULT NULL,
+ `username` varchar(255) DEFAULT NULL,
+ `userhomeurl` varchar(255) DEFAULT NULL,
+ `date_collected` date DEFAULT NULL,
+ `topic_resource` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=451234 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `rep_statics`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `rep_statics` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `commits_num` int(11) DEFAULT NULL,
+ `uname` varchar(255) DEFAULT NULL,
+ `email` varchar(255) DEFAULT NULL,
+ `add` int(11) DEFAULT NULL,
+ `del` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `changeset` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1475 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `repertoires`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `repertoires` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `repositories`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `repositories` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) NOT NULL DEFAULT '0',
+ `url` varchar(255) NOT NULL DEFAULT '',
+ `login` varchar(60) DEFAULT '',
+ `password` varchar(255) DEFAULT '',
+ `root_url` varchar(255) DEFAULT '',
+ `type` varchar(255) DEFAULT NULL,
+ `path_encoding` varchar(64) DEFAULT NULL,
+ `log_encoding` varchar(64) DEFAULT NULL,
+ `extra_info` text,
+ `identifier` varchar(255) DEFAULT NULL,
+ `is_default` tinyint(1) DEFAULT '0',
+ `hidden` tinyint(1) DEFAULT '0',
+ `shixun_id` int(11) DEFAULT NULL,
+ `myshixun_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_repositories_on_project_id` (`project_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=75046 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `resource_banks`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `resource_banks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `course_id` int(11) DEFAULT NULL,
+ `attachment_id` int(11) DEFAULT NULL,
+ `filename` varchar(255) DEFAULT NULL,
+ `disk_filename` varchar(255) DEFAULT NULL,
+ `filesize` int(11) DEFAULT NULL,
+ `digest` varchar(255) DEFAULT NULL,
+ `downloads` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `description` text,
+ `disk_directory` varchar(255) DEFAULT NULL,
+ `is_public` tinyint(1) DEFAULT NULL,
+ `copy_from` int(11) DEFAULT NULL,
+ `quotes` int(11) DEFAULT NULL,
+ `applicable_syllabus` varchar(255) DEFAULT NULL,
+ `major_level` int(11) DEFAULT NULL,
+ `discipline_category_id` int(11) DEFAULT NULL,
+ `first_level_discipline_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `content_type` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_resource_banks_on_course_id` (`course_id`),
+ KEY `index_resource_banks_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8798 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `rich_rich_files`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `rich_rich_files` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `rich_file_file_name` varchar(255) DEFAULT NULL,
+ `rich_file_content_type` varchar(255) DEFAULT NULL,
+ `rich_file_file_size` int(11) DEFAULT NULL,
+ `rich_file_updated_at` datetime DEFAULT NULL,
+ `owner_type` varchar(255) DEFAULT NULL,
+ `owner_id` int(11) DEFAULT NULL,
+ `uri_cache` text,
+ `simplified_type` varchar(255) DEFAULT 'file',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `roles`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `roles` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `position` int(11) DEFAULT '1',
+ `assignable` tinyint(1) DEFAULT '1',
+ `builtin` int(11) NOT NULL DEFAULT '0',
+ `permissions` text,
+ `issues_visibility` varchar(30) NOT NULL DEFAULT 'default',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `schema_migrations`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `schema_migrations` (
+ `version` varchar(255) NOT NULL,
+ UNIQUE KEY `unique_schema_migrations` (`version`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `schools`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `schools` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `province` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `logo_link` varchar(255) DEFAULT NULL,
+ `pinyin` varchar(255) DEFAULT NULL,
+ `school_type` int(11) DEFAULT '0',
+ `city` varchar(255) DEFAULT NULL,
+ `address` varchar(255) DEFAULT NULL,
+ `auto_users_trial` tinyint(1) DEFAULT '0',
+ `shool_code` varchar(255) DEFAULT NULL,
+ `authorization_time` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3159 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `score_indicator_qualities`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `score_indicator_qualities` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `upper_limit` float DEFAULT NULL,
+ `lower_limit` float DEFAULT NULL,
+ `score_set_id` int(11) DEFAULT NULL,
+ `position` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `score_indicators`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `score_indicators` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `score_set_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `scale` float DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `score_quality_descriptions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `score_quality_descriptions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `score_indicator_id` int(11) DEFAULT NULL,
+ `position` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `score_sets`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `score_sets` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `secdomains`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `secdomains` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `sub_type` int(11) DEFAULT NULL,
+ `subname` varchar(255) DEFAULT NULL,
+ `pid` int(11) DEFAULT '0',
+ `desc` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `seems_rateable_cached_ratings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `seems_rateable_cached_ratings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `cacheable_id` bigint(20) DEFAULT NULL,
+ `cacheable_type` varchar(255) DEFAULT NULL,
+ `avg` float NOT NULL,
+ `cnt` int(11) NOT NULL,
+ `dimension` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3294 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `seems_rateable_rates`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `seems_rateable_rates` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `rater_id` bigint(20) DEFAULT NULL,
+ `rateable_id` int(11) DEFAULT NULL,
+ `rateable_type` varchar(255) DEFAULT NULL,
+ `stars` float NOT NULL,
+ `dimension` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `is_teacher_score` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=21336 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `settings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `settings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `value` text,
+ `updated_on` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_settings_on_name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=77 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shares`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shares` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `created_on` date DEFAULT NULL,
+ `url` varchar(255) DEFAULT NULL,
+ `title` varchar(255) DEFAULT NULL,
+ `share_type` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shield_activities`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shield_activities` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `container_type` varchar(255) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `shield_type` varchar(255) DEFAULT NULL,
+ `shield_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=153 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shield_wechat_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shield_wechat_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `shield_id` int(11) DEFAULT NULL,
+ `shield_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shixun_major_courses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shixun_major_courses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `course_list_id` int(11) DEFAULT NULL,
+ `major_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_shixun_major_courses_on_shixun_id` (`shixun_id`),
+ KEY `index_shixun_major_courses_on_major_id` (`major_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4082 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shixun_members`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shixun_members` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ `role` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2413 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shixun_mirror_repositories`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shixun_mirror_repositories` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `mirror_repository_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3953 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shixun_modifies`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shixun_modifies` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `myshixun_id` int(11) DEFAULT NULL,
+ `status` tinyint(4) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4547 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shixun_ports`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shixun_ports` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `port` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shixun_schools`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shixun_schools` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `school_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=170 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shixun_tag_repertoires`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shixun_tag_repertoires` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `shixun_id` int(11) DEFAULT NULL,
+ `tag_repertoire_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1020 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `shixuns`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `shixuns` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `user_id` int(11) DEFAULT NULL,
+ `gpid` int(11) DEFAULT NULL,
+ `visits` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `status` int(11) DEFAULT '0',
+ `language` varchar(255) DEFAULT NULL,
+ `authentication` tinyint(1) DEFAULT '0',
+ `identifier` varchar(255) DEFAULT NULL,
+ `propaedeutics` longtext,
+ `trainee` int(11) DEFAULT '1',
+ `major_id` int(11) DEFAULT NULL,
+ `webssh` int(11) DEFAULT '0',
+ `homepage_show` tinyint(1) DEFAULT '0',
+ `hidden` tinyint(1) DEFAULT '0',
+ `fork_from` int(11) DEFAULT NULL,
+ `can_copy` tinyint(1) DEFAULT '0',
+ `modify_time` datetime DEFAULT NULL,
+ `reset_time` datetime DEFAULT NULL,
+ `publish_time` datetime DEFAULT NULL,
+ `closer_id` int(11) DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `git_url` varchar(255) DEFAULT NULL,
+ `vnc` tinyint(1) DEFAULT '0',
+ `myshixuns_count` int(11) DEFAULT '0' COMMENT '学习人数',
+ `challenges_count` int(11) DEFAULT '0',
+ `use_scope` tinyint(4) DEFAULT '0',
+ `evaluate_script` longtext,
+ `mirror_script_id` int(11) DEFAULT NULL,
+ `image_text` varchar(60) DEFAULT NULL,
+ `code_hidden` tinyint(1) DEFAULT '0',
+ `task_pass` tinyint(1) DEFAULT '0',
+ `exec_time` int(11) DEFAULT '120',
+ PRIMARY KEY (`id`),
+ KEY `index_shixuns_on_identifier` (`identifier`),
+ KEY `index_shixuns_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1116 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `softapplications`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `softapplications` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `app_type_id` int(11) DEFAULT NULL,
+ `app_type_name` varchar(255) DEFAULT NULL,
+ `android_min_version_available` varchar(255) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `contest_id` int(11) DEFAULT NULL,
+ `softapplication_id` int(11) DEFAULT NULL,
+ `is_public` int(11) DEFAULT NULL,
+ `application_developers` varchar(255) DEFAULT NULL,
+ `deposit_project_url` varchar(255) DEFAULT NULL,
+ `deposit_project` varchar(255) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=77 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `sonar_errors`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `sonar_errors` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `jenkins_job_name` varchar(255) DEFAULT NULL,
+ `output` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `ssos`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `ssos` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `openid` varchar(255) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `password` varchar(255) DEFAULT NULL,
+ `email` varchar(255) DEFAULT NULL,
+ `sex` int(11) DEFAULT NULL,
+ `school` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_ssos_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=89 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `stage_shixuns`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stage_shixuns` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `subject_id` int(11) DEFAULT NULL,
+ `stage_id` int(11) DEFAULT NULL,
+ `shixun_id` int(11) DEFAULT NULL,
+ `position` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_stage_shixuns_on_subject_id` (`subject_id`),
+ KEY `index_stage_shixuns_on_stage_id` (`stage_id`),
+ KEY `index_stage_shixuns_on_shixun_id` (`shixun_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1216 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `stages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `stages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `subject_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `user_id` int(11) DEFAULT NULL,
+ `position` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_stages_on_subject_id` (`subject_id`),
+ KEY `index_stages_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=254 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `student_work_projects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `student_work_projects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `student_work_id` int(11) DEFAULT NULL,
+ `project_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `is_leader` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_student_work_projects_on_homework_common_id` (`homework_common_id`),
+ KEY `index_student_work_projects_on_user_id` (`user_id`),
+ KEY `index_student_work_projects_on_project_id` (`project_id`),
+ KEY `index_student_work_projects_on_student_work_id` (`student_work_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4970 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `student_work_tests`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `student_work_tests` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `student_work_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `status` int(11) DEFAULT '9',
+ `results` text,
+ `src` text,
+ PRIMARY KEY (`id`),
+ KEY `index_student_work_tests_on_student_work_id` (`student_work_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=373663 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `student_works`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `student_works` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` longtext,
+ `homework_common_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `final_score` float DEFAULT NULL,
+ `teacher_score` float DEFAULT NULL,
+ `student_score` float DEFAULT NULL,
+ `teaching_asistant_score` float DEFAULT NULL,
+ `project_id` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `late_penalty` int(11) DEFAULT '0',
+ `absence_penalty` int(11) DEFAULT '0',
+ `system_score` float DEFAULT '0',
+ `is_test` tinyint(1) DEFAULT '0',
+ `simi_id` int(11) DEFAULT NULL,
+ `simi_value` int(11) DEFAULT NULL,
+ `work_score` float DEFAULT NULL,
+ `work_status` int(11) DEFAULT '0',
+ `commit_time` datetime DEFAULT NULL,
+ `is_delete` int(11) DEFAULT '0',
+ `appeal_penalty` int(11) DEFAULT '0',
+ `re_commit` tinyint(1) DEFAULT '0',
+ `late_reason` text,
+ `group_id` int(11) DEFAULT '0',
+ `myshixun_id` int(11) DEFAULT '0',
+ `update_time` datetime DEFAULT NULL,
+ `commit_user_id` int(11) DEFAULT NULL,
+ `ultimate_score` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_student_works_on_homework_common_id_and_user_id` (`homework_common_id`,`user_id`),
+ KEY `myshixun_id` (`myshixun_id`),
+ KEY `index_student_works_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=578531 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `student_works_evaluation_distributions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `student_works_evaluation_distributions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `student_work_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=156340 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `student_works_scores`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `student_works_scores` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `student_work_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `score` int(11) DEFAULT NULL,
+ `comment` text,
+ `reviewer_role` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `appeal_status` int(11) DEFAULT '0',
+ `is_hidden` tinyint(1) DEFAULT '0',
+ `is_ultimate` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `user_id` (`user_id`),
+ KEY `student_work_id` (`student_work_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=142316 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `student_works_scores_appeals`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `student_works_scores_appeals` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `student_works_score_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `comment` text,
+ `appeal_status` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `students_for_courses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `students_for_courses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `student_id` int(11) DEFAULT NULL,
+ `course_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_students_for_courses_on_student_id` (`student_id`),
+ KEY `index_students_for_courses_on_course_id` (`course_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=53883 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `sub_document_comments`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `sub_document_comments` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `content` text,
+ `title` text,
+ `sub_domain_id` int(11) DEFAULT NULL,
+ `creator_id` int(11) DEFAULT NULL,
+ `parent_id` int(11) DEFAULT NULL,
+ `reply_id` int(11) DEFAULT NULL,
+ `locked` int(11) DEFAULT NULL,
+ `sticky` int(11) DEFAULT NULL,
+ `org_subfield_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `sub_domains`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `sub_domains` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `org_subfield_id` int(11) DEFAULT NULL,
+ `priority` int(11) DEFAULT '0',
+ `name` varchar(255) DEFAULT NULL,
+ `field_type` varchar(255) DEFAULT NULL,
+ `hide` int(11) DEFAULT '0',
+ `status` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `sub_repertoires`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `sub_repertoires` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `repertoire_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_sub_repertoires_on_repertoire_id` (`repertoire_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `subfield_subdomain_dirs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `subfield_subdomain_dirs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `org_subfield_id` int(11) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `subject_members`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `subject_members` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `subject_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `role` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_subject_members_on_subject_id` (`subject_id`),
+ KEY `index_subject_members_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `subjects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `subjects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `user_id` int(11) DEFAULT NULL,
+ `visits` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `course_list_id` int(11) DEFAULT NULL,
+ `major_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `hidden` tinyint(1) DEFAULT '0',
+ `learning_notes` text,
+ `introduction` varchar(255) DEFAULT NULL,
+ `stages_count` int(11) DEFAULT '0',
+ `stage_shixuns_count` int(11) DEFAULT '0',
+ `homepage_show` tinyint(1) DEFAULT '0',
+ `repertoire_id` int(11) DEFAULT NULL,
+ `score_count` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_subjects_on_user_id` (`user_id`),
+ KEY `index_subjects_on_course_list_id` (`course_list_id`),
+ KEY `index_subjects_on_major_id` (`major_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=84 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `syllabus_members`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `syllabus_members` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `rank` int(11) DEFAULT NULL,
+ `syllabus_id` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_syllabus_members_on_syllabus_id` (`syllabus_id`),
+ KEY `index_syllabus_members_on_user_id` (`user_id`),
+ KEY `index_syllabus_members_on_rank` (`rank`)
+) ENGINE=InnoDB AUTO_INCREMENT=1009 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `syllabus_update_records`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `syllabus_update_records` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `syllabus_id` int(11) DEFAULT NULL,
+ `property` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_syllabus_update_records_on_user_id` (`user_id`),
+ KEY `index_syllabus_update_records_on_syllabus_id` (`syllabus_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `syllabuses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `syllabuses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `title` varchar(255) DEFAULT NULL,
+ `description` text,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `eng_name` varchar(255) DEFAULT NULL,
+ `syllabus_type` int(11) DEFAULT NULL,
+ `credit` int(11) DEFAULT NULL,
+ `hours` int(11) DEFAULT NULL,
+ `theory_hours` int(11) DEFAULT NULL,
+ `practice_hours` int(11) DEFAULT NULL,
+ `applicable_major` varchar(255) DEFAULT NULL,
+ `pre_course` varchar(255) DEFAULT NULL,
+ `visits` int(11) DEFAULT '0',
+ `des_status` int(11) DEFAULT '0',
+ `major_level` int(11) DEFAULT NULL,
+ `discipline_category_id` int(11) DEFAULT NULL,
+ `first_level_discipline_id` int(11) DEFAULT NULL,
+ `major_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_syllabuses_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=997 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `system_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `system_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `content` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `description` text,
+ `subject` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `system_update_notices`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `system_update_notices` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `subject` varchar(255) DEFAULT NULL,
+ `notes` text,
+ `start_time` datetime DEFAULT NULL,
+ `end_time` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `notice_type` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `tag_repertoires`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tag_repertoires` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `sub_repertoire_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=165 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `taggings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `taggings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `tag_id` int(11) DEFAULT NULL,
+ `taggable_id` int(11) DEFAULT NULL,
+ `taggable_type` varchar(255) DEFAULT NULL,
+ `tagger_id` int(11) DEFAULT NULL,
+ `tagger_type` varchar(255) DEFAULT NULL,
+ `context` varchar(128) DEFAULT NULL,
+ `created_at` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_taggings_on_tag_id` (`tag_id`),
+ KEY `index_taggings_on_taggable_id_and_taggable_type_and_context` (`taggable_id`,`taggable_type`,`context`),
+ KEY `index_taggings_on_taggable_type` (`taggable_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=907227 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `tags`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tags` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=135634 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `teachers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `teachers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `tea_name` varchar(255) DEFAULT NULL,
+ `location` varchar(255) DEFAULT NULL,
+ `couurse_time` int(11) DEFAULT NULL,
+ `course_code` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `extra` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `test_sets`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `test_sets` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `input` varchar(255) DEFAULT NULL,
+ `output` text,
+ `challenge_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `is_public` tinyint(1) DEFAULT '1',
+ `result` tinyint(1) DEFAULT '1',
+ `position` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `testset` (`challenge_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=20790 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `tidings`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tidings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `trigger_user_id` int(11) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `parent_container_id` int(11) DEFAULT NULL,
+ `parent_container_type` varchar(255) DEFAULT NULL,
+ `belong_container_id` int(11) DEFAULT NULL,
+ `belong_container_type` varchar(255) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `viewed` tinyint(1) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `tiding_type` varchar(255) DEFAULT NULL,
+ `extra` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=123332 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `time_entries`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `time_entries` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `issue_id` int(11) DEFAULT NULL,
+ `hours` float NOT NULL,
+ `comments` varchar(255) DEFAULT NULL,
+ `activity_id` int(11) NOT NULL,
+ `spent_on` date NOT NULL,
+ `tyear` int(11) NOT NULL,
+ `tmonth` int(11) NOT NULL,
+ `tweek` int(11) NOT NULL,
+ `created_on` datetime NOT NULL,
+ `updated_on` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `time_entries_project_id` (`project_id`),
+ KEY `time_entries_issue_id` (`issue_id`),
+ KEY `index_time_entries_on_activity_id` (`activity_id`),
+ KEY `index_time_entries_on_user_id` (`user_id`),
+ KEY `index_time_entries_on_created_on` (`created_on`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `tokens`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `tokens` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL DEFAULT '0',
+ `action` varchar(30) NOT NULL DEFAULT '',
+ `value` varchar(40) NOT NULL DEFAULT '',
+ `created_on` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `tokens_value` (`value`),
+ KEY `index_tokens_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=116513 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `trackers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `trackers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(30) NOT NULL DEFAULT '',
+ `is_in_chlog` tinyint(1) NOT NULL DEFAULT '0',
+ `position` int(11) DEFAULT '1',
+ `is_in_roadmap` tinyint(1) NOT NULL DEFAULT '1',
+ `fields_bits` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `training_tasks`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `training_tasks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) DEFAULT NULL,
+ `tracker_id` int(11) DEFAULT NULL,
+ `subject` varchar(255) DEFAULT NULL,
+ `description` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `author_id` int(11) DEFAULT NULL,
+ `status` tinyint(4) DEFAULT '0',
+ `position` tinyint(4) DEFAULT '0',
+ `result` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_actions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_actions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `action_type` varchar(255) DEFAULT NULL,
+ `action_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1258332 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_activities`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_activities` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `act_type` varchar(255) DEFAULT NULL,
+ `act_id` int(11) DEFAULT NULL,
+ `container_type` varchar(255) DEFAULT NULL,
+ `container_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `user_act_index` (`act_id`,`act_type`,`container_id`,`created_at`)
+) ENGINE=InnoDB AUTO_INCREMENT=137962 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_day_certifications`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_day_certifications` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_user_day_certifications_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=304 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_extensions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_extensions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL,
+ `birthday` date DEFAULT NULL,
+ `brief_introduction` varchar(255) DEFAULT NULL,
+ `gender` int(11) DEFAULT NULL,
+ `location` varchar(255) DEFAULT NULL,
+ `occupation` varchar(255) DEFAULT NULL,
+ `work_experience` int(11) DEFAULT NULL,
+ `zip_code` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `technical_title` varchar(255) DEFAULT NULL,
+ `identity` int(11) DEFAULT NULL,
+ `student_id` varchar(255) DEFAULT NULL,
+ `teacher_realname` varchar(255) DEFAULT NULL,
+ `student_realname` varchar(255) DEFAULT NULL,
+ `location_city` varchar(255) DEFAULT NULL,
+ `school_id` int(11) DEFAULT NULL,
+ `description` varchar(255) DEFAULT '',
+ `department_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_user_extensions_on_user_id` (`user_id`),
+ KEY `index_user_extensions_on_school_id` (`school_id`),
+ KEY `index_user_extensions_on_department_id` (`department_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=35959 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_feedback_messages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_feedback_messages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `journals_for_message_id` int(11) DEFAULT NULL,
+ `journals_for_message_type` varchar(255) DEFAULT NULL,
+ `viewed` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_user_feedback_messages_on_user_id_and_created_at` (`user_id`,`created_at`),
+ KEY `index_user_feedback_messages_on_journals_for_message_id` (`journals_for_message_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=30409 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_grades`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_grades` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL,
+ `project_id` int(11) NOT NULL,
+ `grade` float DEFAULT '0',
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_user_grades_on_grade` (`grade`),
+ KEY `index_user_grades_on_project_id` (`project_id`),
+ KEY `index_user_grades_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7683 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_hidden_modules`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_hidden_modules` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `module_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_user_hidden_modules_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=932 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_levels`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_levels` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `level` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=6330 DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_preferences`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_preferences` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL DEFAULT '0',
+ `others` text,
+ `hide_mail` tinyint(1) DEFAULT '0',
+ `time_zone` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_user_preferences_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=34261 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_score_details`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_score_details` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `current_user_id` int(11) DEFAULT NULL,
+ `target_user_id` int(11) DEFAULT NULL,
+ `score_type` varchar(255) DEFAULT NULL,
+ `score_action` varchar(255) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `old_score` int(11) DEFAULT NULL,
+ `new_score` int(11) DEFAULT NULL,
+ `current_user_level` int(11) DEFAULT NULL,
+ `target_user_level` int(11) DEFAULT NULL,
+ `score_changeable_obj_id` int(11) DEFAULT NULL,
+ `score_changeable_obj_type` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=55181 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_scores`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_scores` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL,
+ `collaboration` int(11) DEFAULT NULL,
+ `influence` int(11) DEFAULT NULL,
+ `skill` int(11) DEFAULT NULL,
+ `active` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10128 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_searches`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_searches` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `search_type` int(11) DEFAULT NULL,
+ `subject` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_user_searches_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1273 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_statuses`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_statuses` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `changesets_count` int(11) DEFAULT NULL,
+ `watchers_count` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `grade` float DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_user_statuses_on_changesets_count` (`changesets_count`),
+ KEY `index_user_statuses_on_watchers_count` (`watchers_count`),
+ KEY `index_user_statuses_on_grade` (`grade`)
+) ENGINE=InnoDB AUTO_INCREMENT=35170 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_system_notices`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_system_notices` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `notice_type` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_user_system_notices_on_user_id` (`user_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=1214 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `user_wechats`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `user_wechats` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `subscribe` int(11) DEFAULT NULL,
+ `openid` varchar(255) DEFAULT NULL,
+ `nickname` varchar(255) DEFAULT NULL,
+ `sex` int(11) DEFAULT NULL,
+ `language` varchar(255) DEFAULT NULL,
+ `city` varchar(255) DEFAULT NULL,
+ `province` varchar(255) DEFAULT NULL,
+ `country` varchar(255) DEFAULT NULL,
+ `headimgurl` varchar(255) DEFAULT NULL,
+ `subscribe_time` varchar(255) DEFAULT NULL,
+ `unionid` varchar(255) DEFAULT NULL,
+ `remark` varchar(255) DEFAULT NULL,
+ `groupid` int(11) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `bindtype` int(11) DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2424 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `users`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `users` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `login` varchar(255) NOT NULL DEFAULT '',
+ `hashed_password` varchar(40) NOT NULL DEFAULT '',
+ `firstname` varchar(30) NOT NULL DEFAULT '',
+ `lastname` varchar(255) NOT NULL DEFAULT '',
+ `mail` varchar(60) NOT NULL DEFAULT '',
+ `admin` tinyint(1) NOT NULL DEFAULT '0',
+ `status` int(11) NOT NULL DEFAULT '1',
+ `last_login_on` datetime DEFAULT NULL,
+ `language` varchar(5) DEFAULT '',
+ `auth_source_id` int(11) DEFAULT NULL,
+ `created_on` datetime DEFAULT NULL,
+ `updated_on` datetime DEFAULT NULL,
+ `type` varchar(255) DEFAULT NULL,
+ `identity_url` varchar(255) DEFAULT NULL,
+ `mail_notification` varchar(255) NOT NULL DEFAULT '',
+ `salt` varchar(64) DEFAULT NULL,
+ `gid` int(11) DEFAULT NULL,
+ `visits` int(11) DEFAULT '0',
+ `excellent_teacher` int(11) DEFAULT '0',
+ `excellent_student` int(11) DEFAULT '0',
+ `phone` varchar(255) DEFAULT NULL,
+ `authentication` tinyint(1) DEFAULT '0',
+ `grade` int(11) DEFAULT NULL,
+ `experience` int(11) DEFAULT '0',
+ `nickname` varchar(255) DEFAULT NULL,
+ `show_realname` tinyint(1) DEFAULT '1',
+ `professional_certification` tinyint(1) DEFAULT '0',
+ `ID_number` varchar(255) DEFAULT NULL,
+ `certification` int(11) DEFAULT '0',
+ `homepage_teacher` tinyint(1) DEFAULT '0',
+ `homepage_engineer` tinyint(1) DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `index_users_on_id_and_type` (`id`,`type`),
+ KEY `index_users_on_auth_source_id` (`auth_source_id`),
+ KEY `index_users_on_type` (`type`),
+ KEY `index_users_on_homepage_engineer` (`homepage_engineer`),
+ KEY `index_users_on_homepage_teacher` (`homepage_teacher`)
+) ENGINE=InnoDB AUTO_INCREMENT=36346 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `users_authentications`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `users_authentications` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `authentication_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `verification_codes`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `verification_codes` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `code` varchar(255) DEFAULT NULL,
+ `code_type` int(11) DEFAULT NULL,
+ `status` int(11) DEFAULT NULL,
+ `phone` varchar(255) DEFAULT NULL,
+ `email` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=27716 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `versions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `versions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) NOT NULL DEFAULT '0',
+ `name` varchar(255) NOT NULL DEFAULT '',
+ `description` varchar(255) DEFAULT '',
+ `effective_date` date DEFAULT NULL,
+ `created_on` datetime DEFAULT NULL,
+ `updated_on` datetime DEFAULT NULL,
+ `wiki_page_title` varchar(255) DEFAULT NULL,
+ `status` varchar(255) DEFAULT 'open',
+ `sharing` varchar(255) NOT NULL DEFAULT 'none',
+ `user_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `versions_project_id` (`project_id`),
+ KEY `index_versions_on_sharing` (`sharing`)
+) ENGINE=InnoDB AUTO_INCREMENT=456 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `visitors`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `visitors` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `master_id` int(11) DEFAULT NULL,
+ `updated_on` datetime DEFAULT NULL,
+ `created_on` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_visitors_user_id` (`user_id`),
+ KEY `index_visitors_master_id` (`master_id`),
+ KEY `index_visitors_updated_on` (`updated_on`)
+) ENGINE=InnoDB AUTO_INCREMENT=61423 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `watchers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `watchers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `watchable_type` varchar(255) NOT NULL DEFAULT '',
+ `watchable_id` int(11) NOT NULL DEFAULT '0',
+ `user_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `watchers_user_id_type` (`user_id`,`watchable_type`),
+ KEY `index_watchers_on_user_id` (`user_id`),
+ KEY `index_watchers_on_watchable_id_and_watchable_type` (`watchable_id`,`watchable_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=36722 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `web_footer_companies`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `web_footer_companies` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `logo_size` varchar(255) DEFAULT NULL,
+ `url` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `web_footer_oranizers`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `web_footer_oranizers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `description` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `websshes`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `websshes` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `myshixun_id` int(11) DEFAULT NULL,
+ `host` varchar(255) DEFAULT NULL,
+ `port` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `wechat_logs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `wechat_logs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `openid` varchar(255) NOT NULL,
+ `request_raw` text,
+ `response_raw` text,
+ `session_raw` text,
+ `created_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_wechat_logs_on_openid` (`openid`)
+) ENGINE=InnoDB AUTO_INCREMENT=119051 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `wiki_content_versions`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `wiki_content_versions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `wiki_content_id` int(11) NOT NULL,
+ `page_id` int(11) NOT NULL,
+ `author_id` int(11) DEFAULT NULL,
+ `data` longblob,
+ `compression` varchar(6) DEFAULT '',
+ `comments` varchar(255) DEFAULT '',
+ `updated_on` datetime NOT NULL,
+ `version` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `wiki_content_versions_wcid` (`wiki_content_id`),
+ KEY `index_wiki_content_versions_on_updated_on` (`updated_on`)
+) ENGINE=InnoDB AUTO_INCREMENT=264 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `wiki_contents`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `wiki_contents` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `page_id` int(11) NOT NULL,
+ `author_id` int(11) DEFAULT NULL,
+ `text` longtext,
+ `comments` varchar(255) DEFAULT '',
+ `updated_on` datetime NOT NULL,
+ `version` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `wiki_contents_page_id` (`page_id`),
+ KEY `index_wiki_contents_on_author_id` (`author_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `wiki_pages`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `wiki_pages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `wiki_id` int(11) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `created_on` datetime NOT NULL,
+ `protected` tinyint(1) NOT NULL DEFAULT '0',
+ `parent_id` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `wiki_pages_wiki_id_title` (`wiki_id`,`title`),
+ KEY `index_wiki_pages_on_wiki_id` (`wiki_id`),
+ KEY `index_wiki_pages_on_parent_id` (`parent_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `wiki_redirects`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `wiki_redirects` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `wiki_id` int(11) NOT NULL,
+ `title` varchar(255) DEFAULT NULL,
+ `redirects_to` varchar(255) DEFAULT NULL,
+ `created_on` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `wiki_redirects_wiki_id_title` (`wiki_id`,`title`),
+ KEY `index_wiki_redirects_on_wiki_id` (`wiki_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `wikis`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `wikis` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `project_id` int(11) NOT NULL,
+ `start_page` varchar(255) NOT NULL,
+ `status` int(11) NOT NULL DEFAULT '1',
+ PRIMARY KEY (`id`),
+ KEY `wikis_project_id` (`project_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2825 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `work_detail_groups`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `work_detail_groups` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `work_id` int(11) DEFAULT NULL,
+ `min_num` int(11) DEFAULT NULL,
+ `max_num` int(11) DEFAULT NULL,
+ `base_on_project` tinyint(1) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_work_detail_groups_on_work_id` (`work_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `work_detail_manuals`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `work_detail_manuals` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `evaluation_start` date DEFAULT NULL,
+ `evaluation_end` date DEFAULT NULL,
+ `evaluation_num` int(11) DEFAULT NULL,
+ `work_id` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `index_work_detail_manuals_on_work_id` (`work_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `workflows`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `workflows` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `tracker_id` int(11) NOT NULL DEFAULT '0',
+ `old_status_id` int(11) NOT NULL DEFAULT '0',
+ `new_status_id` int(11) NOT NULL DEFAULT '0',
+ `role_id` int(11) NOT NULL DEFAULT '0',
+ `assignee` tinyint(1) NOT NULL DEFAULT '0',
+ `author` tinyint(1) NOT NULL DEFAULT '0',
+ `type` varchar(30) DEFAULT NULL,
+ `field_name` varchar(30) DEFAULT NULL,
+ `rule` varchar(30) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `wkfs_role_tracker_old_status` (`role_id`,`tracker_id`,`old_status_id`),
+ KEY `index_workflows_on_old_status_id` (`old_status_id`),
+ KEY `index_workflows_on_role_id` (`role_id`),
+ KEY `index_workflows_on_new_status_id` (`new_status_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=626 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `works`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `works` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) DEFAULT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `description` text,
+ `publish_time` date DEFAULT NULL,
+ `end_time` date DEFAULT NULL,
+ `work_type` int(11) DEFAULT NULL,
+ `contest_id` int(11) DEFAULT NULL,
+ `is_delete` tinyint(1) DEFAULT '0',
+ `score_open` tinyint(1) DEFAULT '0',
+ `is_open` tinyint(1) DEFAULT '0',
+ `work_status` int(11) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ `online_evaluation` tinyint(1) DEFAULT '0',
+ `score_valid` tinyint(1) DEFAULT '1',
+ PRIMARY KEY (`id`),
+ KEY `index_works_on_user_id` (`user_id`),
+ KEY `index_works_on_contest_id` (`contest_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `works_categories`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `works_categories` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `category` varchar(255) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+DROP TABLE IF EXISTS `zip_packs`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `zip_packs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) DEFAULT NULL,
+ `homework_id` int(11) DEFAULT NULL,
+ `file_digest` varchar(255) DEFAULT NULL,
+ `file_path` varchar(255) DEFAULT NULL,
+ `pack_times` int(11) DEFAULT '1',
+ `pack_size` int(11) DEFAULT '0',
+ `file_digests` text,
+ `created_at` datetime NOT NULL,
+ `updated_at` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=25828 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+INSERT INTO `schema_migrations` (version) VALUES
+('1'),
+('10'),
+('100'),
+('101'),
+('102'),
+('103'),
+('104'),
+('105'),
+('106'),
+('107'),
+('108'),
+('11'),
+('12'),
+('13'),
+('14'),
+('15'),
+('16'),
+('17'),
+('18'),
+('19'),
+('2'),
+('20'),
+('20090214190337'),
+('20090312172426'),
+('20090312194159'),
+('20090318181151'),
+('20090323224724'),
+('20090401221305'),
+('20090401231134'),
+('20090403001910'),
+('20090406161854'),
+('20090425161243'),
+('20090503121501'),
+('20090503121505'),
+('20090503121510'),
+('20090614091200'),
+('20090704172350'),
+('20090704172355'),
+('20090704172358'),
+('20091010093521'),
+('20091017212227'),
+('20091017212457'),
+('20091017212644'),
+('20091017212938'),
+('20091017213027'),
+('20091017213113'),
+('20091017213151'),
+('20091017213228'),
+('20091017213257'),
+('20091017213332'),
+('20091017213444'),
+('20091017213536'),
+('20091017213642'),
+('20091017213716'),
+('20091017213757'),
+('20091017213835'),
+('20091017213910'),
+('20091017214015'),
+('20091017214107'),
+('20091017214136'),
+('20091017214236'),
+('20091017214308'),
+('20091017214336'),
+('20091017214406'),
+('20091017214440'),
+('20091017214519'),
+('20091017214611'),
+('20091017214644'),
+('20091017214720'),
+('20091017214750'),
+('20091025163651'),
+('20091108092559'),
+('20091114105931'),
+('20091123212029'),
+('20091205124427'),
+('20091220183509'),
+('20091220183727'),
+('20091220184736'),
+('20091225164732'),
+('20091227112908'),
+('20100129193402'),
+('20100129193813'),
+('20100221100219'),
+('20100313132032'),
+('20100313171051'),
+('20100705164950'),
+('20100819172912'),
+('20101104182107'),
+('20101107130441'),
+('20101114115114'),
+('20101114115359'),
+('20110220160626'),
+('20110223180944'),
+('20110223180953'),
+('20110224000000'),
+('20110226120112'),
+('20110226120132'),
+('20110227125750'),
+('20110228000000'),
+('20110228000100'),
+('20110401192910'),
+('20110408103312'),
+('20110412065600'),
+('20110511000000'),
+('20110902000000'),
+('20111201201315'),
+('20120115143024'),
+('20120115143100'),
+('20120115143126'),
+('20120127174243'),
+('20120205111326'),
+('20120223110929'),
+('20120301153455'),
+('20120422150750'),
+('20120705074331'),
+('20120707064544'),
+('20120714122000'),
+('20120714122100'),
+('20120714122200'),
+('20120731164049'),
+('20120930112914'),
+('20121026002032'),
+('20121026003537'),
+('20121209123234'),
+('20121209123358'),
+('20121213084931'),
+('20130110122628'),
+('20130201184705'),
+('20130202090625'),
+('20130207175206'),
+('20130207181455'),
+('20130215073721'),
+('20130215111127'),
+('20130215111141'),
+('20130217094251'),
+('20130418001207-redmine_ckeditor'),
+('20130418001208-redmine_ckeditor'),
+('20130418001209-redmine_ckeditor'),
+('20130418001210-redmine_ckeditor'),
+('20130725021433'),
+('20130725122407'),
+('20130725132508'),
+('20130727021306'),
+('20130728021709'),
+('20130801081314'),
+('20130805131602'),
+('20130806072429'),
+('20130806083151'),
+('20130806083152'),
+('20130807021235'),
+('20130807021309'),
+('20130807031901'),
+('20130809122945'),
+('20130810014337'),
+('20130810085341'),
+('20130811001727'),
+('20130811122119'),
+('20130814084938'),
+('20130819020004'),
+('20130823133435'),
+('20130823143552'),
+('20130827003308'),
+('20130828004955'),
+('20130828080407'),
+('20130831080808'),
+('20130831080955'),
+('20130904075504'),
+('20130904135804'),
+('20130906124330'),
+('20130910075221'),
+('20130911005626'),
+('20130911135608'),
+('20130911140019'),
+('20130911140205'),
+('20130913005337'),
+('20130913125835'),
+('20130917123036'),
+('20130918004629'),
+('20130918085747'),
+('20130922123727'),
+('20130922123849'),
+('20130925031313'),
+('20130926005448'),
+('20130926140427'),
+('20130927011824'),
+('20130929011921'),
+('20131009074454'),
+('20131017080750'),
+('20131017120541'),
+('20131017131615'),
+('20131017135933'),
+('20131017140104'),
+('20131017140123'),
+('20131021024144'),
+('20131031071414'),
+('20131031071452'),
+('20131031093317'),
+('20131107073302'),
+('20131108133857'),
+('20131112005309'),
+('20131112015232'),
+('20131113124237'),
+('20131122020026'),
+('20131122132942'),
+('20131215065910'),
+('20131224021723'),
+('20140318011702'),
+('20140318021747'),
+('20140318072309'),
+('20140319092720'),
+('20140320015156'),
+('20140320022724'),
+('20140324015819'),
+('20140327071420'),
+('20140327073052'),
+('20140327082704'),
+('20140401004102'),
+('20140401121611'),
+('20140402023357'),
+('20140403075029'),
+('20140403113341'),
+('20140404030103'),
+('20140404031622'),
+('20140410015850'),
+('20140410020848'),
+('20140410021724'),
+('20140411003234'),
+('20140411004155'),
+('20140411005214'),
+('20140411011700'),
+('20140413022725'),
+('20140414012423'),
+('20140415090718'),
+('20140415090829'),
+('20140417085550'),
+('20140417090022'),
+('20140417091429'),
+('20140421044829'),
+('20140421044830'),
+('20140424022002'),
+('20140424072458'),
+('20140428005537'),
+('20140428013546'),
+('20140505082635'),
+('20140505083218'),
+('20140505083430'),
+('20140508030039'),
+('20140508030358'),
+('20140509020307'),
+('20140513073801'),
+('20140515013449'),
+('20140516031200'),
+('20140519020211'),
+('20140519030825'),
+('20140519054846'),
+('20140519070751'),
+('20140519074133'),
+('20140521072851'),
+('20140522025137'),
+('20140522025721'),
+('20140526031949'),
+('20140526104509'),
+('20140527060344'),
+('20140529084427'),
+('20140530010015'),
+('20140530102014'),
+('20140603033359'),
+('20140603042015'),
+('20140603081801'),
+('20140604011630'),
+('20140604071623'),
+('20140604071624'),
+('20140605003915'),
+('20140605025247'),
+('20140605025300'),
+('20140605025302'),
+('20140605025303'),
+('20140606027403'),
+('20140606028512'),
+('20140609061903'),
+('20140611161801'),
+('20140617013146'),
+('20140617092219'),
+('20140618020535'),
+('20140618105213'),
+('20140618155324'),
+('20140626012511'),
+('20140701031909'),
+('20140701075839'),
+('20140703011335'),
+('20140703085204'),
+('20140704034832'),
+('20140707095213'),
+('20140708023356'),
+('20140710024054'),
+('20140710030426'),
+('20140710071720'),
+('20140710095123'),
+('20140711010124'),
+('20140711012924'),
+('20140714021812'),
+('20140714081030'),
+('20140715015540'),
+('20140716021202'),
+('20140716021558'),
+('20140718091306'),
+('20140719030741'),
+('20140719030941'),
+('20140719080032'),
+('20140721074353'),
+('20140722024513'),
+('20140722080529'),
+('20140722080924'),
+('20140723082637'),
+('20140724080319'),
+('20140725013735'),
+('20140725062302'),
+('20140725073357'),
+('20140728014933'),
+('20140730021521'),
+('20140730024419'),
+('20140801034242'),
+('20140811022947'),
+('20140812032957'),
+('20140812065147'),
+('20140812065417'),
+('20140814062455'),
+('20140826072838'),
+('20140916005319'),
+('20140922032830'),
+('20140930072719'),
+('20141009010934'),
+('20141009055029'),
+('20141013014908'),
+('20141013023400'),
+('20141029065917'),
+('20141031065238'),
+('20141031111632'),
+('20141031122331'),
+('20141102054414'),
+('20141103015148'),
+('20141103032156'),
+('20141103065703'),
+('20141105012624'),
+('20141119011439'),
+('20141120091234'),
+('20141126091207'),
+('20141126091750'),
+('20141127015431'),
+('20141127072548'),
+('20141201085218'),
+('20141210070327'),
+('20141226074532'),
+('20141229025519'),
+('20141229025925'),
+('20141229081716'),
+('20141229141201'),
+('20141230011546'),
+('20141230034253'),
+('20141230062844'),
+('20141230081209'),
+('20141230081744'),
+('20141231020031'),
+('20141231085350'),
+('20150108034148'),
+('20150108034253'),
+('20150108034414'),
+('20150108035301'),
+('20150108035338'),
+('20150112024820'),
+('20150112080435'),
+('20150114022710'),
+('20150121030451'),
+('20150123020615'),
+('20150128032421'),
+('20150206023634'),
+('20150206060632'),
+('20150210062236'),
+('20150227061944'),
+('20150227065713'),
+('20150227083257'),
+('20150227085333'),
+('20150302061232'),
+('20150302091345'),
+('20150305011023'),
+('20150305011359'),
+('20150305081132'),
+('20150309090143'),
+('20150311013036'),
+('20150316032155'),
+('20150316083717'),
+('20150318025244'),
+('20150324021043'),
+('20150328115230'),
+('20150331031554'),
+('20150331032810'),
+('20150402015402'),
+('20150409092151'),
+('20150414115406'),
+('20150415032102'),
+('20150422034543'),
+('20150428021035'),
+('20150505023015'),
+('20150505023127'),
+('20150505023452'),
+('20150505025003'),
+('20150505025537'),
+('20150510100343'),
+('20150514133640'),
+('20150519012744'),
+('20150519014600'),
+('20150519014639'),
+('20150519020031'),
+('20150519022200'),
+('20150519023821'),
+('20150519030544'),
+('20150528024616'),
+('20150601032112'),
+('20150602021020'),
+('20150602055730'),
+('20150604153000'),
+('20150619060110'),
+('20150630031857'),
+('20150702073245'),
+('20150702073308'),
+('20150708025533'),
+('20150708085629'),
+('20150709071731'),
+('20150712063406'),
+('20150713161100'),
+('20150714161100'),
+('20150714162200'),
+('20150715070534'),
+('20150715162300'),
+('20150719092427'),
+('20150722015428'),
+('20150730093403'),
+('20150730130816'),
+('20150801034945'),
+('20150810064247'),
+('20150811010817'),
+('20150811065543'),
+('20150811080754'),
+('20150811083322'),
+('20150814011838'),
+('20150814024425'),
+('20150814031258'),
+('20150815030833'),
+('20150819090720'),
+('20150820004659'),
+('20150820022416'),
+('20150820025358'),
+('20150824133916'),
+('20150826020407'),
+('20150826061843'),
+('20150828011415'),
+('20150828155329'),
+('20150829023459'),
+('20150829024549'),
+('20150829081822'),
+('20150829130302'),
+('20150831070611'),
+('20150831093918'),
+('20150901004812'),
+('20150901004910'),
+('20150906025009'),
+('20150906065702'),
+('20150906083453'),
+('20150906090419'),
+('20150906091723'),
+('20150907064144'),
+('20150907064547'),
+('20150907152238'),
+('20150909062619'),
+('20150911031029'),
+('20150911064528'),
+('20150914063751'),
+('20150915063302'),
+('20150917022239'),
+('20150917071652'),
+('20150917081214'),
+('20150918004521'),
+('20150918005722'),
+('20150918063404'),
+('20150918134804'),
+('20150924063215'),
+('20150925025200'),
+('20150925060939'),
+('20150928090128'),
+('20150930011457'),
+('20151013023237'),
+('20151013081912'),
+('20151013091057'),
+('20151013092356'),
+('20151014012627'),
+('20151014013243'),
+('20151014023806'),
+('20151020013352'),
+('20151020014759'),
+('20151020021234'),
+('20151022071611'),
+('20151022071804'),
+('20151028060607'),
+('20151029030006'),
+('20151102083844'),
+('20151102084419'),
+('20151102085318'),
+('20151102090519'),
+('20151103011119'),
+('20151104020233'),
+('20151104024335'),
+('20151104032831'),
+('20151104070007'),
+('20151104070455'),
+('20151104073902'),
+('20151104090032'),
+('20151109073857'),
+('20151109080256'),
+('20151110011003'),
+('20151112072948'),
+('20151113025341'),
+('20151113025459'),
+('20151113025524'),
+('20151113025549'),
+('20151113025721'),
+('20151113025751'),
+('20151116020842'),
+('20151116065904'),
+('20151116071721'),
+('20151117033430'),
+('20151117075939'),
+('20151118014720'),
+('20151118015638'),
+('20151118031602'),
+('20151119124148'),
+('20151120021958'),
+('20151120115137'),
+('20151120134208'),
+('20151124032319'),
+('20151125064914'),
+('20151126160252'),
+('20151127011351'),
+('20151130031446'),
+('20151130032658'),
+('20151130033906'),
+('20151130064556'),
+('20151202064455'),
+('20151203030635'),
+('20151203072815'),
+('20151204030143'),
+('20151204062220'),
+('20151208015409'),
+('20151208025236'),
+('20151208032013'),
+('20151208073241'),
+('20151209085900'),
+('20151209085942'),
+('20151215070238'),
+('20151215105425'),
+('20151216025539'),
+('20151216030610'),
+('20151217051447'),
+('20151218022014'),
+('20151218110033'),
+('20151223062932'),
+('20151224090313'),
+('20151229022049'),
+('20151229045505'),
+('20151230015410'),
+('20151230015904'),
+('20151230022443'),
+('20151231012634'),
+('20151231023235'),
+('20151231023610'),
+('20160104082423'),
+('20160105014033'),
+('20160105073350'),
+('20160106065255'),
+('20160107050736'),
+('20160108021447'),
+('20160108024752'),
+('20160108093752'),
+('20160111064927'),
+('20160111065137'),
+('20160111065215'),
+('20160111065530'),
+('20160111071348'),
+('20160111071411'),
+('20160111071529'),
+('20160111071558'),
+('20160111080833'),
+('20160111080914'),
+('20160112085834'),
+('20160113023045'),
+('20160113023137'),
+('20160113024927'),
+('20160113063514'),
+('20160113064153'),
+('20160113090435'),
+('20160114022833'),
+('20160114022928'),
+('20160114131753'),
+('20160115021923'),
+('20160115022341'),
+('20160115023749'),
+('20160115125217'),
+('20160116034925'),
+('20160118014219'),
+('20160118083751'),
+('20160119034447'),
+('20160120032758'),
+('20160121070232'),
+('20160122023014'),
+('20160122083400'),
+('20160122083507'),
+('20160122094805'),
+('20160122094829'),
+('20160122142844'),
+('20160122143138'),
+('20160126024429'),
+('20160126031857'),
+('20160128024452'),
+('20160202034530'),
+('20160220100507'),
+('20160222064143'),
+('20160223021227'),
+('20160223031843'),
+('20160223073859'),
+('20160224032046'),
+('20160224074034'),
+('20160225024759'),
+('20160303103231'),
+('20160304154005'),
+('20160304234903'),
+('20160309022930'),
+('20160309024051'),
+('20160309072649'),
+('20160310033019'),
+('20160311072540'),
+('20160311072622'),
+('20160311072718'),
+('20160311072819'),
+('20160316055201'),
+('20160317070611'),
+('20160317090350'),
+('20160321071740'),
+('20160321073042'),
+('20160321073107'),
+('20160321073227'),
+('20160321075815'),
+('20160321080116'),
+('20160321080336'),
+('20160321080412'),
+('20160321080825'),
+('20160321085313'),
+('20160322032610'),
+('20160324052634'),
+('20160324074942'),
+('20160325030146'),
+('20160325030423'),
+('20160328022312'),
+('20160328022623'),
+('20160329014316'),
+('20160330095711'),
+('20160331063938'),
+('20160405021915'),
+('20160408074854'),
+('20160414055511'),
+('20160414060838'),
+('20160415025623'),
+('20160415030447'),
+('20160418074429'),
+('20160419061745'),
+('20160419074016'),
+('20160421011543'),
+('20160426084709'),
+('20160427061847'),
+('20160427070237'),
+('20160428065243'),
+('20160429015956'),
+('20160429030819'),
+('20160504060751'),
+('20160506085852'),
+('20160506104128'),
+('20160509025404'),
+('20160511055221'),
+('20160513012705'),
+('20160513021204'),
+('20160513120002'),
+('20160517013659'),
+('20160517091224'),
+('20160518031514'),
+('20160518060243'),
+('20160519070718'),
+('20160523085440'),
+('20160526093715'),
+('20160531021244'),
+('20160601073753'),
+('20160606064856'),
+('20160612030537'),
+('20160612043259'),
+('20160613064914'),
+('20160613065840'),
+('20160614072229'),
+('20160622033322'),
+('20160622074138'),
+('20160624032138'),
+('20160624054614'),
+('20160624055127'),
+('20160624103411'),
+('20160627074232'),
+('20160627090316'),
+('20160629030320'),
+('20160629081520'),
+('20160629084146'),
+('20160629094716'),
+('20160630112733'),
+('20160707031248'),
+('20160708005533'),
+('20160708091258'),
+('20160709015740'),
+('20160715091215'),
+('20160718064146'),
+('20160719013955'),
+('20160720094503'),
+('20160721075236'),
+('20160722074421'),
+('20160725062343'),
+('20160725091759'),
+('20160727020247'),
+('20160727065357'),
+('20160728041513'),
+('20160728041943'),
+('20160728075947'),
+('20160729020903'),
+('20160729124038'),
+('20160729124833'),
+('20160810080942'),
+('20160810081337'),
+('20160811084401'),
+('20160824073554'),
+('20160830090214'),
+('20160905084821'),
+('20160907055119'),
+('20160907061917'),
+('20160913063446'),
+('20160914073340'),
+('20160918024056'),
+('20160918024214'),
+('20160918033136'),
+('20160918074635'),
+('20160921062340'),
+('20160923024443'),
+('20161008015936'),
+('20161009053958'),
+('20161011012114'),
+('20161014085016'),
+('20161015013348'),
+('20161015054820'),
+('20161015102324'),
+('20161018082432'),
+('20161019020422'),
+('20161020055047'),
+('20161027070249'),
+('20161028053000'),
+('20161111064007'),
+('20161111070615'),
+('20161111071624'),
+('20161111081619'),
+('20161114092115'),
+('20161115082005'),
+('20161117015856'),
+('20161117060138'),
+('20161121063025'),
+('20161121091826'),
+('20161125024643'),
+('20161128030405'),
+('20161128072528'),
+('20161129032534'),
+('20161129033440'),
+('20161129084352'),
+('20161130031415'),
+('20161201015458'),
+('20161201073217'),
+('20161201083030'),
+('20161202024209'),
+('20161202073523'),
+('20161206061652'),
+('20161208015939'),
+('20161213074134'),
+('20161214060728'),
+('20161214080008'),
+('20161216031906'),
+('20161220062249'),
+('20161220090638'),
+('20161220090826'),
+('20161220093609'),
+('20161221055334'),
+('20161221060341'),
+('20161221060853'),
+('20161221065228'),
+('20161221065841'),
+('20161221070253'),
+('20161221070623'),
+('20161222033007'),
+('20161222063638'),
+('20161223025155'),
+('20161223030701'),
+('20161223083022'),
+('20161227085308'),
+('20161228070235'),
+('20161228091749'),
+('20161230005953'),
+('20161230061940'),
+('20170105024224'),
+('20170106024520'),
+('20170111021557'),
+('20170111030006'),
+('20170112072122'),
+('20170112082231'),
+('20170117161330'),
+('20170119072629'),
+('20170119084215'),
+('20170120021457'),
+('20170120120614'),
+('20170207060207'),
+('20170209020934'),
+('20170210082105'),
+('20170217004513'),
+('20170217092541'),
+('20170217092859'),
+('20170217104309'),
+('20170218074506'),
+('20170219025424'),
+('20170219062646'),
+('20170219070127'),
+('20170220060000'),
+('20170220060210'),
+('20170220065632'),
+('20170222072150'),
+('20170227074837'),
+('20170228133958'),
+('20170302020239'),
+('20170302032957'),
+('20170302073836'),
+('20170302094330'),
+('20170303030731'),
+('20170303071513'),
+('20170306070453'),
+('20170307082304'),
+('20170307092931'),
+('20170307093928'),
+('20170309024921'),
+('20170309062850'),
+('20170310011834'),
+('20170310014056'),
+('20170310032024'),
+('20170310053903'),
+('20170310072101'),
+('20170314072825'),
+('20170314081550'),
+('20170314082632'),
+('20170314085929'),
+('20170315103005'),
+('20170317022123'),
+('20170317022508'),
+('20170317022557'),
+('20170320054808'),
+('20170321081658'),
+('20170321102722'),
+('20170322033103'),
+('20170322095511'),
+('20170323081518'),
+('20170324054735'),
+('20170324090202'),
+('20170328064900'),
+('20170328065349'),
+('20170328065735'),
+('20170328081152'),
+('20170328082148'),
+('20170330084904'),
+('20170331013652'),
+('20170401055830'),
+('20170405075018'),
+('20170410085204'),
+('20170410085945'),
+('20170410092257'),
+('20170411013813'),
+('20170411093519'),
+('20170412023151'),
+('20170412063958'),
+('20170412075557'),
+('20170413030447'),
+('20170413032641'),
+('20170413064458'),
+('20170413065659'),
+('20170414023452'),
+('20170414024741'),
+('20170414084212'),
+('20170416064210'),
+('20170417081351'),
+('20170419070551'),
+('20170419112951'),
+('20170419132939'),
+('20170420080141'),
+('20170420081959'),
+('20170425064652'),
+('20170425072017'),
+('20170425085926'),
+('20170425092430'),
+('20170426020613'),
+('20170426024708'),
+('20170426024822'),
+('20170426060122'),
+('20170427011453'),
+('20170427013052'),
+('20170427031044'),
+('20170502083644'),
+('20170502091943'),
+('20170503062237'),
+('20170503062507'),
+('20170503075321'),
+('20170503085743'),
+('20170504021749'),
+('20170504065145'),
+('20170504070210'),
+('20170505025616'),
+('20170505032131'),
+('20170505085001'),
+('20170508081347'),
+('20170509080544'),
+('20170511020541'),
+('20170515070741'),
+('20170515071837'),
+('20170515073053'),
+('20170517020032'),
+('20170518015629'),
+('20170518064248'),
+('20170522062153'),
+('20170522071931'),
+('20170522130310'),
+('20170523011829'),
+('20170526022745'),
+('20170526023023'),
+('20170526062330'),
+('20170526062907'),
+('20170526063836'),
+('20170526073920'),
+('20170526090359'),
+('20170602021309'),
+('20170607015139'),
+('20170607021803'),
+('20170609061623'),
+('20170614020255'),
+('20170615070234'),
+('20170615080332'),
+('20170615105442'),
+('20170619091405'),
+('20170621023044'),
+('20170622013100'),
+('20170623030431'),
+('20170626022454'),
+('20170626031135'),
+('20170626055814'),
+('20170627073830'),
+('20170704074622'),
+('20170705064602'),
+('20170705071652'),
+('20170705073342'),
+('20170705083812'),
+('20170705090958'),
+('20170705091953'),
+('20170706024154'),
+('20170706071415'),
+('20170711091159'),
+('20170712023923'),
+('20170713024020'),
+('20170713030017'),
+('20170717083910'),
+('20170718013325'),
+('20170718022342'),
+('20170718080353'),
+('20170720013004'),
+('20170721015724'),
+('20170721022106'),
+('20170721060845'),
+('20170728080308'),
+('20170728082516'),
+('20170802012507'),
+('20170804062817'),
+('20170807063211'),
+('20170808021426'),
+('20170808033437'),
+('20170810012934'),
+('20170811055836'),
+('20170817092910'),
+('20170822032004'),
+('20170822032223'),
+('20170822032810'),
+('20170822062358'),
+('20170822080604'),
+('20170824103529'),
+('20170825031026'),
+('20170825032003'),
+('20170825113413'),
+('20170827023117'),
+('20170829021122'),
+('20170830032124'),
+('20170830093154'),
+('20170831015104'),
+('20170831015613'),
+('20170831022201'),
+('20170831022550'),
+('20170831092348'),
+('20170904071627'),
+('20170904082600'),
+('20170904091629'),
+('20170907021641'),
+('20170907025441'),
+('20170907061723'),
+('20170907100131'),
+('20170908020109'),
+('20170908115908'),
+('20170911033152'),
+('20170912024832'),
+('20170912090828'),
+('20170914013811'),
+('20170914024926'),
+('20170915085332'),
+('20170918014832'),
+('20170918090054'),
+('20170918092326'),
+('20170920022714'),
+('20170920065705'),
+('20170921084836'),
+('20170922015144'),
+('20170922091816'),
+('20170922112450'),
+('20170922114504'),
+('20170926012936'),
+('20170927015114'),
+('20170930023907'),
+('20170930024003'),
+('20170930081820'),
+('20170930082930'),
+('20170930122738'),
+('20171010071751'),
+('20171011084944'),
+('20171012073054'),
+('20171012101648'),
+('20171013081923'),
+('20171013083722'),
+('20171013115401'),
+('20171014070618'),
+('20171014074021'),
+('20171014080052'),
+('20171014095120'),
+('20171019065522'),
+('20171020011305'),
+('20171023115525'),
+('20171024014724'),
+('20171024015602'),
+('20171024015824'),
+('20171024020826'),
+('20171024030645'),
+('20171024081725'),
+('20171026105403'),
+('20171027090248'),
+('20171027090356'),
+('20171027090458'),
+('20171027110314'),
+('20171030070711'),
+('20171030072123'),
+('20171030085925'),
+('20171031025843'),
+('20171031062353'),
+('20171031072755'),
+('20171101020347'),
+('20171101022807'),
+('20171103031157'),
+('20171106030726'),
+('20171106083916'),
+('20171107013142'),
+('20171108032131'),
+('20171108080344'),
+('20171109022624'),
+('20171109060716'),
+('20171109065843'),
+('20171110083046'),
+('20171113062848'),
+('20171113084709'),
+('20171114070152'),
+('20171115023919'),
+('20171115032026'),
+('20171115071239'),
+('20171115084833'),
+('20171116015944'),
+('20171117015511'),
+('20171117031934'),
+('20171117082126'),
+('20171117093339'),
+('20171121063613'),
+('20171121072336'),
+('20171121080141'),
+('20171122015449'),
+('20171122080410'),
+('20171122081525'),
+('20171122091118'),
+('20171123022414'),
+('20171123065703'),
+('20171123070319'),
+('20171124071537'),
+('20171124072840'),
+('20171124080022'),
+('20171124080516'),
+('20171124081330'),
+('20171124120750'),
+('20171124122621'),
+('20171128032158'),
+('20171129075555'),
+('20171201092823'),
+('20171208020535'),
+('20171208031726'),
+('20171208092415'),
+('20171211091713'),
+('20171212082837'),
+('20171212093226'),
+('20171212100049'),
+('20171213062609'),
+('20171213064646'),
+('20171213072616'),
+('20171213073348'),
+('20171214021853'),
+('20171214023018'),
+('20171214025420'),
+('20171214063652'),
+('20171214084429'),
+('20171214085019'),
+('20171215010708'),
+('20171215113239'),
+('20171218031524'),
+('20171219033623'),
+('20171219054106'),
+('20171219062652'),
+('20171220013630'),
+('20171221021043'),
+('20171221054254'),
+('20171221074055'),
+('20171221083438'),
+('20171221092115'),
+('20171222013450'),
+('20171222022504'),
+('20171222060821'),
+('20171222071113'),
+('20171222075831'),
+('20171222132047'),
+('20171222152033'),
+('20171225085155'),
+('20171226060732'),
+('20171226065503'),
+('20171226070302'),
+('20171226075551'),
+('20171226092333'),
+('20171226101132'),
+('20171227065103'),
+('20171227083935'),
+('20171228085816'),
+('20171228122117'),
+('20171229082949'),
+('20171229094541'),
+('20180102022853'),
+('20180104013923'),
+('20180105023335'),
+('20180105093310'),
+('20180109081109'),
+('20180110012234'),
+('20180112121731'),
+('20180115083549'),
+('20180116030420'),
+('20180118060556'),
+('20180118073148'),
+('20180118090936'),
+('20180119083918'),
+('20180122071811'),
+('20180123082641'),
+('20180124114615'),
+('20180129090133'),
+('20180131061311'),
+('20180131064555'),
+('20180131070154'),
+('20180201091245'),
+('20180202063413'),
+('20180202091641'),
+('20180204022311'),
+('20180205083404'),
+('20180206083254'),
+('20180207013445'),
+('20180207020017'),
+('20180207024825'),
+('20180207081311'),
+('20180228024546'),
+('20180228024951'),
+('20180309075023'),
+('20180313060218'),
+('20180314022506'),
+('20180314070401'),
+('20180316064901'),
+('20180316065917'),
+('20180316071234'),
+('20180316082825'),
+('20180316123807'),
+('20180318014622'),
+('20180321072457'),
+('20180321082129'),
+('20180328074357'),
+('20180330025749'),
+('20180330071500'),
+('20180403082849'),
+('20180404031923'),
+('20180404032519'),
+('20180404060342'),
+('20180408031817'),
+('20180409082423'),
+('20180409085809'),
+('20180410005959'),
+('20180410010815'),
+('20180410011903'),
+('20180410012604'),
+('20180410023305'),
+('20180410033026'),
+('20180411015053'),
+('20180411021857'),
+('20180411030750'),
+('20180411072015'),
+('20180413012742'),
+('20180415033808'),
+('20180416070001'),
+('20180416073019'),
+('20180416082938'),
+('20180418023500'),
+('20180418023608'),
+('20180418065309'),
+('20180420025051'),
+('20180420030114'),
+('20180420072903'),
+('20180424014459'),
+('20180424032725'),
+('20180425083336'),
+('20180503074404'),
+('20180504155310'),
+('20180511031742'),
+('20180515013318'),
+('20180517032433'),
+('20180517074054'),
+('20180521030537'),
+('20180522042604'),
+('20180522062045'),
+('20180522063657'),
+('20180523021343'),
+('20180523033339'),
+('20180523072755'),
+('20180524072611'),
+('20180525091540'),
+('20180525092331'),
+('20180529064425'),
+('20180531084726'),
+('20180531091113'),
+('20180531104205'),
+('20180601080626'),
+('20180604080213'),
+('20180606091107'),
+('20180607022655'),
+('20180608013258'),
+('20180608021656'),
+('20180608082745'),
+('20180612092939'),
+('20180613071648'),
+('20180614032357'),
+('20180625080716'),
+('20180627092727'),
+('20180628062932'),
+('20180629064058'),
+('20180629112409'),
+('20180703025928'),
+('20180703030830'),
+('20180710031834'),
+('20180712024228'),
+('20180713005958'),
+('20180713092051'),
+('20180713093156'),
+('20180716032319'),
+('20180716061450'),
+('20180718024306'),
+('20180718024839'),
+('20180718085746'),
+('20180719020018'),
+('20180720081025'),
+('20180723074456'),
+('20180724010018'),
+('20180726020714'),
+('20180730023048'),
+('20180730024428'),
+('20180730082738'),
+('20180731020501'),
+('20180731023338'),
+('20180731023445'),
+('20180731024106'),
+('20180801005016'),
+('20180801080506'),
+('20180805042542'),
+('20180806030714'),
+('20180806031346'),
+('21'),
+('22'),
+('23'),
+('24'),
+('25'),
+('26'),
+('27'),
+('28'),
+('29'),
+('3'),
+('30'),
+('31'),
+('32'),
+('33'),
+('34'),
+('35'),
+('36'),
+('37'),
+('38'),
+('39'),
+('4'),
+('40'),
+('41'),
+('42'),
+('43'),
+('44'),
+('45'),
+('46'),
+('47'),
+('48'),
+('49'),
+('5'),
+('50'),
+('51'),
+('52'),
+('53'),
+('54'),
+('55'),
+('56'),
+('57'),
+('58'),
+('59'),
+('6'),
+('60'),
+('61'),
+('62'),
+('63'),
+('64'),
+('65'),
+('66'),
+('67'),
+('68'),
+('69'),
+('7'),
+('70'),
+('71'),
+('72'),
+('73'),
+('74'),
+('75'),
+('76'),
+('77'),
+('78'),
+('79'),
+('8'),
+('80'),
+('81'),
+('82'),
+('83'),
+('84'),
+('85'),
+('86'),
+('87'),
+('88'),
+('89'),
+('9'),
+('90'),
+('91'),
+('92'),
+('93'),
+('94'),
+('95'),
+('96'),
+('97'),
+('98'),
+('99');
+
+
diff --git a/lib/assets/.keep b/lib/assets/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/lib/educoder.rb b/lib/educoder.rb
new file mode 100644
index 000000000..0a07701ae
--- /dev/null
+++ b/lib/educoder.rb
@@ -0,0 +1,11 @@
+#coding=utf-8
+#
+#
+module Educoder
+ # 调用时才加载
+ autoload :TipException, "educoder/tip_exception"
+ autoload :Utils, "educoder/units"
+ autoload :Sms, "educoder/sms"
+ autoload :I18n, "educoder/i18n"
+ autoload :Ufile, "educoder/ufile"
+end
\ No newline at end of file
diff --git a/lib/educoder/i18n.rb b/lib/educoder/i18n.rb
new file mode 100644
index 000000000..4675f9537
--- /dev/null
+++ b/lib/educoder/i18n.rb
@@ -0,0 +1,167 @@
+module Educoder
+ module I18n
+ def self.included(base)
+ base.extend Educoder::I18n
+ end
+
+ def l(*args)
+ case args.size
+ when 1
+ ::I18n.t(*args)
+ when 2
+ if args.last.is_a?(Hash)
+ ::I18n.t(*args)
+ elsif args.last.is_a?(String)
+ ::I18n.t(args.first, :value => args.last)
+ else
+ ::I18n.t(args.first, :count => args.last)
+ end
+ else
+ raise "Translation string with multiple values: #{args.first}"
+ end
+ end
+
+ def l_or_humanize(s, options={})
+ k = "#{options[:prefix]}#{s}".to_sym
+ ::I18n.t(k, :default => s.to_s.humanize)
+ end
+
+ def l_hours(hours)
+ hours = hours.to_f
+ l((hours < 2.0 ? :label_f_hour : :label_f_hour_plural), :value => ("%.2f" % hours.to_f))
+ end
+
+ def ll(lang, str, value=nil)
+ ::I18n.t(str.to_s, :value => value, :locale => lang.to_s.gsub(%r{(.+)\-(.+)$}) { "#{$1}-#{$2.upcase}" })
+ end
+
+ def format_date(date)
+ return nil unless date
+ options = {}
+ options[:format] = Setting.date_format unless Setting.date_format.blank?
+ options[:locale] = User.current.language unless User.current.language.blank?
+ ::I18n.l(date.to_date, options)
+ end
+
+ def format_time(time, include_date = true)
+ return nil unless time
+ options = {}
+ options[:format] = (Setting.time_format.blank? ? :time : Setting.time_format)
+ options[:locale] = User.current.language unless User.current.language.blank?
+ time = time.to_time if time.is_a?(String)
+ zone = User.current.time_zone
+ local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
+ (include_date ? "#{format_date(local)} " : "") + ::I18n.l(local, options)
+ end
+
+ def day_name(day)
+ ::I18n.t('date.day_names')[day % 7]
+ end
+
+ def day_letter(day)
+ ::I18n.t('date.abbr_day_names')[day % 7].first
+ end
+
+ def month_name(month)
+ ::I18n.t('date.month_names')[month]
+ end
+
+ def valid_languages
+ ::I18n.available_locales
+ end
+
+ # Returns an array of languages names and code sorted by names, example:
+ # [["Deutsch", "de"], ["English", "en"] ...]
+ #
+ # The result is cached to prevent from loading all translations files.
+ def languages_options
+ ActionController::Base.cache_store.fetch "i18n/languages_options" do
+ valid_languages.map {|lang| [ll(lang.to_s, :general_lang_name), lang.to_s]}.sort {|x,y| x.first <=> y.first }
+ end
+ end
+
+ def find_language(lang)
+ @@languages_lookup = valid_languages.inject({}) {|k, v| k[v.to_s.downcase] = v; k }
+ @@languages_lookup[lang.to_s.downcase]
+ end
+
+ def set_language_if_valid(lang)
+ if l = find_language(lang)
+ ::I18n.locale = l
+ end
+ end
+
+ def current_language
+ ::I18n.locale
+ end
+
+ # Custom backend based on I18n::Backend::Simple with the following changes:
+ # * lazy loading of translation files
+ # * available_locales are determined by looking at translation file names
+ class Backend
+ (class << self; self; end).class_eval { public :include }
+
+ module Implementation
+ include ::I18n::Backend::Base
+
+ # Stores translations for the given locale in memory.
+ # This uses a deep merge for the translations hash, so existing
+ # translations will be overwritten by new ones only at the deepest
+ # level of the hash.
+ def store_translations(locale, data, options = {})
+ locale = locale.to_sym
+ translations[locale] ||= {}
+ data = data.deep_symbolize_keys
+ translations[locale].deep_merge!(data)
+ end
+
+ # Get available locales from the translations filenames
+ def available_locales
+ @available_locales ||= ::I18n.load_path.map {|path| File.basename(path, '.*')}.uniq.sort.map(&:to_sym)
+ end
+
+ # Clean up translations
+ def reload!
+ @translations = nil
+ @available_locales = nil
+ super
+ end
+
+ protected
+
+ def init_translations(locale)
+ locale = locale.to_s
+ paths = ::I18n.load_path.select {|path| File.basename(path, '.*') == locale}
+ load_translations(paths)
+ translations[locale] ||= {}
+ end
+
+ def translations
+ @translations ||= {}
+ end
+
+ # Looks up a translation from the translations hash. Returns nil if
+ # eiher key is nil, or locale, scope or key do not exist as a key in the
+ # nested translations hash. Splits keys or scopes containing dots
+ # into multiple keys, i.e. currency.format is regarded the same as
+ # %w(currency format).
+ def lookup(locale, key, scope = [], options = {})
+ init_translations(locale) unless translations.key?(locale)
+ keys = ::I18n.normalize_keys(locale, key, scope, options[:separator])
+
+ keys.inject(translations) do |result, _key|
+ _key = _key.to_sym
+ return nil unless result.is_a?(Hash) && result.has_key?(_key)
+ result = result[_key]
+ result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
+ result
+ end
+ end
+ end
+
+ include Implementation
+ # Adds fallback to default locale for untranslated strings
+ include ::I18n::Backend::Fallbacks
+ end
+ end
+end
diff --git a/lib/educoder/sms.rb b/lib/educoder/sms.rb
new file mode 100644
index 000000000..9d54a6df5
--- /dev/null
+++ b/lib/educoder/sms.rb
@@ -0,0 +1,77 @@
+#coding=utf-8
+
+require 'net/https'
+require 'uri'
+
+module Educoder
+ module Sms
+ def self.send(opt={})
+ Rails.logger.info "#{opt[:mobile]} - #{opt[:code]}"
+ begin
+ o = sendYunpian(opt[:mobile], opt[:code], opt[:send_type], opt[:name], opt[:user_name], opt[:result])
+ if o["code"] != 0
+ Rails.logger.error "发送短信出错: #{o['code']}--#{o['msg']}"
+ end
+ return o["code"]
+ rescue => e
+ Rails.logger.error "发送短信出错: #{e}"
+ return false
+ end
+ end
+
+ def self.notify_admin(opt)
+ opt[:name] = '管理员'
+ opt[:mobile] = ENV['NOTIDY_ADMIN_PHONE'] || '17680641960'
+ send(opt)
+ end
+
+ def self.sendYunpian(mobile, code, send_type, name, user_name, result)
+ #修改为您的apikey.可在官网(http://www.yunpian.com)登录后用户中心首页看到
+ apikey = EduSetting.find_by_name('sms_apikey').try(:value)
+ #指定模板发送接口HTTP地址
+ send_tpl_sms_uri = URI.parse('https://sms.yunpian.com/v2/sms/single_send.json')
+
+ params = {}
+ params['apikey'] = apikey
+ params['mobile'] = mobile
+ params['text'] = ""
+ if send_type.nil?
+ params['text'] = "【Edu实训】" + code + "(手机验证码)。如非本人操作,请忽略。"
+ elsif send_type == 'competition_start'
+ params['text'] = "【Edu实训】亲爱的#{user_name},你参与的#{name}将于#{result}开始,请及时参赛"
+ Rails.logger.info "#{params['text']}"
+ elsif send_type == 'subject_authorization' || send_type == 'shixun_authorization'
+ params['text'] = "【Edu实训】亲爱的#{user_name},您提交的#{name}#{send_type=='subject_authorization'?'实训路径':'实训'}发布申请#{result},请登录平台查看详情"
+ Rails.logger.info "#{params['text']}"
+ elsif send_type == 'authentication_pro' || send_type == 'authentication'|| send_type == 'trial_authorization' || send_type == 'project_info'
+ params['text'] = "【Edu实训】亲爱的#{user_name},您提交的#{send_type == 'authentication_pro'?'职业认证':(send_type == 'authentication'? '实名认证' : (send_type == 'project_info'?'加入申请':'试用申请' ))}#{result},请登录平台查看详情"
+ Rails.logger.info "#{params['text']}"
+ elsif send_type == "apply_pro_certification" || send_type == "apply_auth"
+ params['text'] = "【Edu实训】亲爱的#{name},有新的#{send_type == 'apply_pro_certification'?'职业':'实名'}认证申请,请尽快处理"
+ Rails.logger.info "#{params['text']}"
+ elsif send_type == "publish_subject" ||send_type == "publish_shixun"|| send_type == "user_apply_auth" || send_type == "discuss"
+ params['text'] = "【Edu实训】亲爱的#{name},有新的#{send_type == 'publish_subject'?'实训路径':(send_type == 'publish_shixun' ? '实训' : (send_type == 'discuss' ? '实训评论':'试用'))}申请发布,请尽快处理"
+ Rails.logger.info "#{params['text']}"
+ elsif send_type == 'join_course_multi_role'
+ params['text'] = "【Edu实训】亲爱的#{user_name},您的课堂#{name}有助教或者教师申请加入,请尽快审核"
+ Rails.logger.info "#{params['text']}"
+ elsif send_type == 'applied_project_info'
+ params['text'] = "【Edu实训】亲爱的#{user_name},您的项目#{name}有成员申请加入,请尽快审核"
+ Rails.logger.info "#{params['text']}"
+ end
+
+ http = Net::HTTP.new(send_tpl_sms_uri.host, send_tpl_sms_uri.port)
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ http.use_ssl = true
+ begin
+ request = Net::HTTP::Post.new(send_tpl_sms_uri.request_uri)
+ request.set_form_data(params)
+ request['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'
+ response = http.start { |http| http.request(request) }
+ ActiveSupport::JSON.decode(response.body)
+ rescue =>err
+ return nil
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/educoder/tip_exception.rb b/lib/educoder/tip_exception.rb
new file mode 100644
index 000000000..09f0228ac
--- /dev/null
+++ b/lib/educoder/tip_exception.rb
@@ -0,0 +1,24 @@
+module Educoder
+
+ class TipException < StandardError
+ attr_reader :status, :message
+
+ def initialize(status=-1, message)
+ case status
+ when 403
+ message = "您没有权限进行该操作"
+ when 404
+ message = "您访问的页面不存在或已被删除"
+ end
+ @status = status
+ @message = message
+
+ Rails.logger.info("############# #{@status}, #{@message}")
+ end
+
+ def tip_json
+ {status: self.status, message: self.message}
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/lib/educoder/ufile.rb b/lib/educoder/ufile.rb
new file mode 100644
index 000000000..2a52b0af6
--- /dev/null
+++ b/lib/educoder/ufile.rb
@@ -0,0 +1,130 @@
+#coding=utf-8
+#
+# ucloud 文件上传
+#
+
+require 'base64'
+require 'openssl'
+require 'faraday'
+
+module Educoder
+ class Ufile
+ PATH_PREFIX = %r{^/}
+
+ def initialize(uploader={})
+ @ucloud_public_key = uploader[:ucloud_public_key]
+ @ucloud_private_key = uploader[:ucloud_private_key]
+ @ucloud_public_read = uploader[:ucloud_public_read]
+ @ucloud_bucket = @ucloud_public_read ? uploader[:ucloud_public_bucket] : uploader[:ucloud_private_bucket]
+ @ucloud_bucket_host = @ucloud_public_read ? uploader[:ucloud_public_bucket_host] : uploader[:ucloud_private_bucket_host]
+ @ucloud_cdn_host = @ucloud_public_read ? uploader[:ucloud_public_cdn_host] : uploader[:ucloud_private_cdn_host]
+ @ucloud_private_expire_seconds = uploader[:ucloud_private_expire_seconds] || 300
+
+ unless @ucloud_cdn_host.include?('//')
+ raise "config.ucloud_cdn_host requirement include // http:// or https://, but you give: #{@ucloud_cdn_host}"
+ end
+ end
+
+ # 上传文件
+ def put(path, file, headers = {})
+ path.sub!(PATH_PREFIX, '')
+
+ response = conn.put(path, file.read) do |req|
+ req.headers = headers
+ token = authorization(req.method, headers['Content-Type'], path)
+ req.headers['Authorization'] = token
+ end
+
+ if response.success?
+ true
+ else
+ raise 'Ucloud上传失败'
+ end
+ end
+
+ # 读取文件
+ def get(path)
+ path.sub!(PATH_PREFIX, '')
+ response = conn.get(url(path))
+
+ if response.success?
+ return response
+ else
+ raise 'Ucloud Get File Fail'
+ end
+ end
+
+ # 删除文件
+ def delete(path)
+ path.sub!(PATH_PREFIX, '')
+ response = conn.delete(url(path)) do |req|
+ req.headers['Authorization'] = authorization(req.method, nil, path)
+ end
+
+ if response.success?
+ true
+ else
+ raise 'Ucloud Get File Fail'
+ end
+ end
+
+ def url(path)
+ if @ucloud_public_read
+ public_get_url(path)
+ else
+ private_get_url(path)
+ end
+ end
+
+ # 公开的访问地址
+ def public_get_url(path)
+ path.sub!(PATH_PREFIX, '')
+ [@ucloud_cdn_host, path].join('/')
+ end
+
+ # 私有空间访问地址
+ def private_get_url(path)
+ public_get_url(path) + privite_get_url_auth(path)
+ end
+
+ private
+
+ def conn
+ @conn ||= begin
+ Faraday.new(url: @ucloud_bucket_host) do |req|
+ req.request :url_encoded
+ req.adapter Faraday.default_adapter
+ end
+ end
+ end
+
+ # 私密查看url的认证信息
+ def privite_get_url_auth(path)
+ expired_ts = private_expire_ts
+ signed_str = signature(string_to_sign('GET', nil, path, expired_ts))
+ "?UCloudPublicKey=#{@ucloud_public_key}&Expires=#{expired_ts}&Signature=#{signed_str}"
+ end
+
+ def private_expire_ts
+ @ucloud_private_expire_seconds + Time.now.to_i
+ end
+
+ def authorization(http_method, content_type, path)
+ signed_str = signature(string_to_sign(http_method, content_type, path))
+ "UCloud " + @ucloud_public_key + ":" + signed_str
+ end
+
+ def signature(str)
+ Base64.strict_encode64(OpenSSL::HMAC.digest('sha1', @ucloud_private_key, str))
+ end
+
+ def string_to_sign(http_method, ori_content_type, path, expired_ts = nil)
+ http_verb = "#{http_method.upcase}\n"
+ content_md5 = "\n"
+ content_type = "#{ori_content_type}\n"
+ timestamp = "#{expired_ts}\n"
+ full_path = "/#{@ucloud_bucket}/#{path}"
+ http_verb + content_md5 + content_type + timestamp + full_path
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/educoder/units.rb b/lib/educoder/units.rb
new file mode 100644
index 000000000..8fca64a5f
--- /dev/null
+++ b/lib/educoder/units.rb
@@ -0,0 +1,144 @@
+require 'fileutils'
+
+module Educoder
+ module Utils
+ class << self
+ # Returns the relative root url of the application
+ def relative_url_root
+ ActionController::Base.respond_to?('relative_url_root') ?
+ ActionController::Base.relative_url_root.to_s :
+ ActionController::Base.config.relative_url_root.to_s
+ end
+
+ # Sets the relative root url of the application
+ def relative_url_root=(arg)
+ if ActionController::Base.respond_to?('relative_url_root=')
+ ActionController::Base.relative_url_root=arg
+ else
+ ActionController::Base.config.relative_url_root = arg
+ end
+ end
+
+ # Generates a n bytes random hex string
+ # Example:
+ # random_hex(4) # => "89b8c729"
+ def random_hex(n)
+ SecureRandom.hex(n)
+ end
+
+ def save_upload(upload, path)
+ directory = File.dirname(path)
+ unless File.exists?(directory)
+ FileUtils.mkdir_p directory
+ end
+ File.open(path, "wb") do |f|
+ if upload.respond_to?(:read)
+ buffer = ""
+ while (buffer = upload.read(8192))
+ f.write(buffer)
+ yield buffer if block_given?
+ end
+ else
+ f.write(upload)
+ yield upload if block_given?
+ end
+ end
+ end
+
+ def digest(diskfile)
+ md5 = Digest::MD5.new
+ File.open(diskfile, "rb") do |f|
+ buffer = ""
+ while (buffer = f.read(8192))
+ md5.update(buffer)
+ end
+ end
+ md5.hexdigest
+ end
+ end
+
+ module Shell
+
+ module_function
+
+ def shell_quote(str)
+ if Redmine::Platform.mswin?
+ '"' + str.gsub(/"/, '\\"') + '"'
+ else
+ "'" + str.gsub(/'/, "'\"'\"'") + "'"
+ end
+ end
+
+ def shell_quote_command(command)
+ if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java'
+ command
+ else
+ shell_quote(command)
+ end
+ end
+ end
+
+ module DateCalculation
+ # Returns the number of working days between from and to
+ def working_days(from, to)
+ days = (to - from).to_i
+ if days > 0
+ weeks = days / 7
+ result = weeks * (7 - non_working_week_days.size)
+ days_left = days - weeks * 7
+ start_cwday = from.cwday
+ days_left.times do |i|
+ unless non_working_week_days.include?(((start_cwday + i - 1) % 7) + 1)
+ result += 1
+ end
+ end
+ result
+ else
+ 0
+ end
+ end
+
+ # Adds working days to the given date
+ def add_working_days(date, working_days)
+ if working_days > 0
+ weeks = working_days / (7 - non_working_week_days.size)
+ result = weeks * 7
+ days_left = working_days - weeks * (7 - non_working_week_days.size)
+ cwday = date.cwday
+ while days_left > 0
+ cwday += 1
+ unless non_working_week_days.include?(((cwday - 1) % 7) + 1)
+ days_left -= 1
+ end
+ result += 1
+ end
+ next_working_date(date + result)
+ else
+ date
+ end
+ end
+
+ # Returns the date of the first day on or after the given date that is a working day
+ def next_working_date(date)
+ cwday = date.cwday
+ days = 0
+ while non_working_week_days.include?(((cwday + days - 1) % 7) + 1)
+ days += 1
+ end
+ date + days
+ end
+
+ # Returns the index of non working week days (1=monday, 7=sunday)
+ def non_working_week_days
+ @non_working_week_days ||= begin
+ days = Setting.non_working_week_days
+ if days.is_a?(Array) && days.size < 7
+ days.map(&:to_i)
+ else
+ []
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab-cli/.rakeTasks b/lib/gitlab-cli/.rakeTasks
new file mode 100644
index 000000000..ea716fd52
--- /dev/null
+++ b/lib/gitlab-cli/.rakeTasks
@@ -0,0 +1,7 @@
+
+The page you were looking for doesn't exist.
+ The change you wanted was rejected.
+ We're sorry, but something went wrong.
+