init project

This commit is contained in:
Jasder
2020-03-09 00:40:16 +08:00
commit 2937b2a94d
6549 changed files with 7215173 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
class ApplicationRecord < ActiveRecord::Base
include NumberDisplayHelper
attr_accessor :_extra_data
self.abstract_class = true
def format_time(time)
time.present? ? time.strftime('%Y-%m-%d %H:%M') : ''
end
def display_extra_data(key)
_extra_data&.[](key)
end
def allow_sync_to_trustie?
Rails.env.production? && EduSetting.get('host_name') == 'https://www.educoder.net'
end
end

View File

@@ -0,0 +1,5 @@
class AppliedMessage < ApplicationRecord
belongs_to :user
belongs_to :applied, polymorphic: true
end

View File

@@ -0,0 +1,9 @@
class AppliedProject < ApplicationRecord
belongs_to :user
belongs_to :project
has_many :applied_messages, as: :applied, dependent: :destroy
has_many :forge_activities, as: :forge_act, dependent: :destroy
scope :pending, -> { where(status: 0) }
end

View File

@@ -0,0 +1,30 @@
# 申请消息
class ApplyAction < ApplicationRecord
belongs_to :user
has_many :tidings, :as => :container, :dependent => :destroy
after_create :send_tiding
def status_text
I18n.t!("apply_action.status.#{status}")
rescue I18n::MissingTranslationData
nil
end
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')
elsif %w(ApplyShixun ApplySubject TrialAuthorization).include?(container_type)
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

View File

@@ -0,0 +1,15 @@
class ApplyAddDepartment < ApplicationRecord
belongs_to :user
belongs_to :school
belongs_to :department
has_many :applied_messages, as: :applied
has_many :tidings, as: :container, dependent: :destroy
after_create :send_notify
private
def send_notify
tidings.create!(user_id: 1, trigger_user_id: user_id, belong_container: school, tiding_type: 'Apply', status: 0)
end
end

View File

@@ -0,0 +1,22 @@
class ApplyAddSchool < ApplicationRecord
belongs_to :school
belongs_to :user
has_many :applied_messages, as: :applied
has_many :tidings, as: :container, dependent: :destroy
after_create :send_notify
# after_destroy :after_delete_apply
private
def send_notify
Tiding.create!(user_id: 1, status: 0, container_id: id, container_type: 'ApplyAddSchools',
trigger_user_id: user_id, belong_container: school, tiding_type: 'Apply')
end
# def after_delete_apply
#
# end
end

View File

@@ -0,0 +1,31 @@
# status0 审核中 1 同意 2 拒绝 3 撤销
# auth_type1 实名认证, 2 职业认证
class ApplyUserAuthentication < ApplicationRecord
belongs_to :user
has_many :tidings, :as => :container, :dependent => :destroy
has_one :attachment, as: :container, dependent: :destroy
scope :real_name_auth, -> { where(auth_type: 1) }
scope :professional_auth, -> { where(auth_type: 2) }
scope :processing, -> { where(status: 0) }
scope :passed, -> { where(status: 1) }
after_create :send_tiding
def status_text
I18n.t!("apply_user_authentication.status.#{status}")
rescue I18n::MissingTranslationData
nil
end
def revoke!
update!(status: 3)
end
private
def send_tiding
self.tidings << Tiding.new(:user_id => '1', :status=> 0, :trigger_user_id => user_id, :belong_container_id => 1, :belong_container_type =>'User', :tiding_type => "Apply")
end
end

145
app/models/attachment.rb Normal file
View File

@@ -0,0 +1,145 @@
class Attachment < ApplicationRecord
include BaseModel
include Publicable
include Publishable
include Lockable
belongs_to :container, polymorphic: 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
# 二级目录
belongs_to :course_second_category, optional: true
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, :content_type, :container_type, :container_id) }
scope :search_by_container, -> (ids) {where(container_id: ids)}
scope :unified_setting, -> {where("unified_setting = ? ", 1)}
validates_length_of :description, maximum: 100, message: "不能超过100个字符"
DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z)
def diskfile
File.join(File.join(Rails.root, "files"), disk_directory.to_s, disk_filename.to_s)
end
def relative_path_filename
File.join(disk_directory.to_s, disk_filename.to_s)
end
def title
title = filename
if container && container.is_a?(StudentWork) && author_id != User.current.id
course = container&.homework_common&.course
unless User.current.teacher_of_course?(course)
title = "#{Time.now.strftime('%Y%m%d%H%M%S')}_#{DCODES.sample(8).join}" + File.extname(filename)
end
end
title
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", "delay_publish").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', "delay_publish")
end
def set_public(is_public)
if is_public == true
is_public = 1
elsif is_public == false
is_public = 0
end
end
#判断是否为pdf文件
def is_pdf?
is_pdf = false
file_content_type = content_type
file_ext_type = File.extname(filename).strip.downcase[1..-1]
if (file_content_type.present? && file_content_type.downcase.include?("pdf")) || (file_ext_type.present? && file_ext_type.include?("pdf"))
is_pdf = true
end
is_pdf
end
end

View File

@@ -0,0 +1,8 @@
class AttachmentGroupSetting < ActiveRecord::Base
belongs_to :attachment
belongs_to :course_group
belongs_to :course
scope :none_published, -> {where("attachment_group_settings.publish_time IS NULL OR attachment_group_settings.publish_time > ?", Time.now)}
end

View File

@@ -0,0 +1,33 @@
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
def is_history_pdf?
is_pdf = false
file_content_type = content_type
file_ext_type = File.extname(filename).strip.downcase[1..-1]
if (file_content_type.present? && file_content_type.downcase.include?("pdf")) || (file_ext_type.present? && file_ext_type.include?("pdf"))
is_pdf = true
end
is_pdf
end
end

16
app/models/attendance.rb Normal file
View File

@@ -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

View File

@@ -0,0 +1,24 @@
class BiddingUser < ApplicationRecord
include AASM
belongs_to :user
belongs_to :project_package, counter_cache: true
aasm(:status) do
state :pending, initial: true
state :bidding_won
state :bidding_lost
event :win do
transitions from: [:pending], to: :bid_won
end
event :lose do
transitions from: [:pending], to: :bid_lost
end
end
def status_text
I18n.t("bidding_user.status.#{status}")
end
end

2
app/models/career.rb Normal file
View File

@@ -0,0 +1,2 @@
class Career < ApplicationRecord
end

150
app/models/challenge.rb Normal file
View File

@@ -0,0 +1,150 @@
class Challenge < ApplicationRecord
# difficulty: 关卡难度: 1.简单 2.中等 3.困难
# show_type: 效果展示:-1.无效果 1.图片 2.apk/exe 3.txt 4.html 5.mp3 6.mp4
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_treads, 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
# 回复
has_many :discusses, :dependent => :destroy
# acts_as_attachable
scope :base_attrs, -> { select([:id, :subject, :position, :shixun_id, :st, :score, :path, :task_pass, :modify_time,
:web_route, :answer, :exec_time, :praises_count]) }
scope :choose_type, -> { where(st: 1) }
scope :practice_type, -> { where(st: 0) }
scope :fields_for_list, -> { select([:id, :subject, :st, :score, :position, :shixun_id]) }
validates :task_pass, length: { maximum: 35000, too_long: "不能超过35000个字符" }
after_commit :create_diff_record
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.score
#self.challenge_chooses.pluck(:score).sum
end
def challenge_difficulty
case difficulty
when 1 then "简单"
when 2 then "中等"
when 3 then "困难"
else ''
end
end
# 关卡总分
def all_score
self.score
# if self.st == 1
# self.choose_score
# else
# self.score
# end
end
# 开启挑战
def open_game user_id, shixun
game = self.games.where(user_id: user_id).first
if game.present?
shixun.task_pass || game.status != 3 ? "/tasks/#{game.identifier}" : ""
else
"/api/shixuns/#{shixun.identifier}/shixun_exec"
end
end
# # 开启挑战
# def open_game(user_id, shixun)
#
#
# game = self.games.select([:status, :identifier]).where(user_id: user_id).first
# game = self.games.select{|game| game.user_id == user_id}
# 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 = games.where(user_id: user_id).take
if game.blank?
position == 1 ? 1 : 0
else
if game.status == 3
shixun.task_pass ? 1 : 0
elsif game.status == 2
2
else
1
end
end
end
def tags_show
if self.challenge_tags.nil?
"--"
else
self.try(:challenge_tags).map(&:name).join(";")
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
# 关卡评测文件
private
def create_diff_record
return unless task_pass_previously_changed?
CreateDiffRecordJob.perform_later(User.current.id, id, 'Challenge', 'task_pass', task_pass_before_last_save, task_pass)
end
end

View File

@@ -0,0 +1,11 @@
class ChallengeAnswer < ApplicationRecord
default_scope { order("challenge_answers.level asc") }
belongs_to :challenge
has_many :game_answers, :dependent => :destroy
validates :contents, length: { maximum: 25000 , too_long: "不能超过25000个字符"}
def view_answer_time(user_id)
game_answers.where(user_id: user_id).last&.view_time
end
end

View File

@@ -0,0 +1,9 @@
class ChallengeChoose < ApplicationRecord
default_scope {order("challenge_chooses.position asc")}
belongs_to :challenge, optional: true
has_many :challenge_tags, :dependent => :destroy
has_many :challenge_questions, dependent: :destroy
validates :subject, length: { maximum: 25000, too_long: "不能超过25000个字符" }
end

View File

@@ -0,0 +1,6 @@
class ChallengeQuestion < ApplicationRecord
belongs_to :challenge_choose
validates :option_name, length: { maximum: 500, too_long: "不能超过500个字符" }
end

View File

@@ -0,0 +1,4 @@
class ChallengeSample < ApplicationRecord
belongs_to :challenge
end

View File

@@ -0,0 +1,6 @@
class ChallengeTag < ApplicationRecord
include Searchable::Dependents::ChallengeTag
belongs_to :challenge, counter_cache: true
belongs_to :challenge_choose, optional: true
end

View File

@@ -0,0 +1,15 @@
class ChallengeWorkScore < ApplicationRecord
belongs_to :user
belongs_to :student_work
belongs_to :challenge
has_many :tidings, as: :container, dependent: :destroy
validates :comment, length: { maximum: 500, too_long: "不能超过500个字符" }
def create_tiding trigger_user_id
tidings << Tiding.new(user_id: student_work.user_id, trigger_user_id: trigger_user_id, container_id: id,
container_type: "ChallengeWorkScore", parent_container_id: student_work_id,
parent_container_type: "StudentWork", belong_container_id: student_work&.homework_common&.course_id,
belong_container_type: "Course", viewed: 0, tiding_type: "HomeworkCommon")
end
end

4
app/models/chart_rule.rb Normal file
View File

@@ -0,0 +1,4 @@
class ChartRule < ApplicationRecord
validates :content, length: { maximum: 1000, too_long: "不能超过1000个字符" }
end

4
app/models/commit.rb Normal file
View File

@@ -0,0 +1,4 @@
class Commit < ApplicationRecord
belongs_to :project, foreign_key: :project_id
end

View File

@@ -0,0 +1,4 @@
class CommitIssue < ApplicationRecord
belongs_to :issue, foreign_key: :issue_id
end

12
app/models/compose.rb Normal file
View File

@@ -0,0 +1,12 @@
class Compose < ApplicationRecord
#组织
belongs_to :user
has_many :compose_projects
has_many :compose_users
validates :title, presence: {message: "组织名称不能为空"}, uniqueness: {message: "组织名称已存在"}
scope :compose_includes, ->{includes(:compose_projects, :compose_users, :user)}
end

View File

@@ -0,0 +1,4 @@
class ComposeProject < ApplicationRecord
#组织的项目记录表
belongs_to :compose
end

View File

@@ -0,0 +1,4 @@
class ComposeUser < ApplicationRecord
belongs_to :compose
belongs_to :user
end

View File

View File

@@ -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

View File

@@ -0,0 +1,21 @@
module Likeable
extend ActiveSupport::Concern
included do
has_many :praise_treads, as: :praise_tread_object, dependent: :destroy
end
def liked?(praiseable)
praiseable.praise_treads.exists?(user_id: self.id)
end
def like!(praiseable)
praiseable.praise_treads.create!(user_id: self.id)
end
def unlike!(praiseable)
obj = praiseable.praise_treads.find_by(user_id: self.id)
obj.destroy! if obj.present?
end
end

View File

@@ -0,0 +1,10 @@
module Lockable
extend ActiveSupport::Concern
included do
end
def locked?(is_member)
is_member == true ? false : !publiced?
end
end

View File

@@ -0,0 +1,13 @@
module Matchable
extend ActiveSupport::Concern
included do
scope :like, lambda { |keywords|
where("name LIKE ?", "%#{keywords.split(" ").join('|')}%") unless keywords.blank?
}
scope :with_project_category, ->(category_id) { where(project_category_id: category_id) unless category_id.blank? }
scope :with_project_language, ->(language_id) { where(project_language_id: language_id) unless language_id.blank? }
scope :with_project_type, ->(project_type) { where(project_type: project_type) if Project.project_types.include?(project_type) }
end
end

View File

@@ -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

View File

@@ -0,0 +1,43 @@
module ProjectOperable
extend ActiveSupport::Concern
included do
has_many :members
# has_many :except_owner_members, -> { members.where("members.use_id != ? ", self.owner.id ) }
has_many :manager_members, -> { joins(:roles).where(roles: { name: 'Manager' }) }, class_name: 'Member'
end
def add_member!(user_id, role_name='Developer')
member = members.create!(user_id: user_id)
set_developer_role(member)
end
def remove_member!(user_id)
member = members.find_by(user_id: user_id)
member.destroy! if member && self.user_id != user_id
end
def member?(user_id)
members.exists?(user_id: user_id)
end
# 除了项目创建者本身
def member(user_id)
members.where.not("members.user_id = ? ", owner.id).find_by(user_id: user_id)
end
def change_member_role!(user_id, role)
member = self.member(user_id)
member.member_roles.last.update_attributes!(role: role)
end
def owner?(user)
self.owner == user
end
def set_developer_role(member)
role = Role.find_by_name 'Developer'
member.member_roles.create!(role: role)
end
end

View File

@@ -0,0 +1,15 @@
module Projectable
extend ActiveSupport::Concern
included do
has_many :projects, -> { order(position: :asc) }
scope :without_content, -> { select(column_names - ['content'])}
scope :search, lambda { |keywords|
where("name LIKE ?", "%#{keywords.split(" ").join('|')}%") unless keywords.blank?
}
end
module ClassMethods
end
end

View File

@@ -0,0 +1,7 @@
module Publicable
extend ActiveSupport::Concern
included do
scope :visible, -> { where(is_public: true) }
end
end

View File

@@ -0,0 +1,9 @@
module Publishable
extend ActiveSupport::Concern
included do
alias_attribute :publish, :is_publish
enum publish: { published: 1, unpublish: 0 }
end
end

View File

@@ -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!(watchable)
watchable.watchers.create!(user: self)
end
def unwatch!(watchable)
obj = watchable.watchers.find_by(user: self)
obj.destroy! if obj.present?
end
module ClassMethods
end
end

5
app/models/coo_img.rb Normal file
View File

@@ -0,0 +1,5 @@
class CooImg < ApplicationRecord
extend Enumerize
enumerize :img_type, in: %i[com_coop edu_coop alliance_coop]
end

View File

@@ -0,0 +1,9 @@
class Cooperation < ApplicationRecord
def user_type_text
case user_type.to_i
when 1 then '高校合作'
when 2 then '企业合作'
when 3 then '实训投稿'
end
end
end

455
app/models/course.rb Normal file
View File

@@ -0,0 +1,455 @@
class Course < ApplicationRecord
include Searchable::Course
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, optional: true
belongs_to :laboratory, optional: true
# 所属实践课程
belongs_to :subject, optional: true
has_many :informs, as: :container, dependent: :destroy
has_many :course_infos, dependent: :destroy
# 课堂左侧导航栏的模块
has_many :course_modules, dependent: :destroy
has_many :none_hidden_course_modules, -> { not_hidden }, class_name: "CourseModule"
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 :students, -> { course_students }, class_name: 'CourseMember'
has_many :teacher_course_members, -> { teachers_and_admin }, class_name: 'CourseMember'
has_many :teacher_users, through: :teacher_course_members, source: :user
has_many :course_messages, dependent: :destroy
has_many :homework_commons, dependent: :destroy
has_many :normal_homeworks, -> { normals }, class_name: 'HomeworkCommon'
has_many :group_homeworks, -> { groups }, class_name: 'HomeworkCommon'
has_many :practice_homeworks, -> { practices }, class_name: 'HomeworkCommon'
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_one :course_act, class_name: 'CourseActivity', as: :course_act, dependent: :destroy
has_many :course_activities
has_many :tidings, as: :container, dependent: :destroy
# 开放课堂
has_many :course_stages, -> { order("course_stages.position ASC") }, dependent: :destroy
has_many :course_stage_shixuns, dependent: :destroy
has_many :shixuns, through: :course_stage_shixuns
# 老版的members弃用 现用course_members
has_many :members
# 视频
has_many :course_videos, dependent: :destroy
has_many :videos, through: :course_videos
# 直播
has_many :live_links, dependent: :destroy
validate :validate_sensitive_string
scope :hidden, ->(is_hidden = true) { where(is_hidden: is_hidden) }
scope :ended, ->(is_end = true) { where(is_end: is_end) }
scope :processing, -> { where(is_end: false) }
scope :not_deleted, -> { where(is_delete: 0) }
scope :not_excellent, -> { where(excellent: 0) }
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?
}
scope :started, -> { where("start_date is null or start_date <= '#{Date.today}'") }
# 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: 60, too_long: "不能超过60个字符" }
before_save :set_laboratory
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 course_group_module?
course_modules.exists?(module_type: "course_group", hidden: 0)
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).size
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 user_group_name(user_id)
students.find_by(user_id: user_id)&.course_group_name
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_ids(user_id)
course_teacher_member = teacher_course_groups.get_user_groups(user_id).select(:course_group_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?
students.where(course_group_id: teachers.pluck(:course_group_id)).pluck(:user_id)
else
students.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 = course_modules.where(module_type: type).first
course_module.update_attribute(:hidden, hidden_value) if course_module.present?
end
end
def all_course_module_types
%w[activity announcement online_learning shixun_homework common_homework group_homework exercise attachment course_group graduation poll board statistics video]
end
def get_course_module_by_type(type)
#CourseModule.where(course_id: course_id, module_type: type).first
self.course_modules.where(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 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_or_business?
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(:course_group_id)
end
#课程动态公共表记录
def act_as_course_activity
self.course_act << 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 = []
publish_groups = course_groups.where(id: common_ids)
if common_ids.include?(0)
teacher_power_courses << {course_name:"未分班", course_id: 0, student_count: none_group_count}
end
if publish_groups.present?
publish_groups.each do |group|
teacher_power_courses << {course_name: group&.name,course_id: group&.id, student_count: group&.course_members_count}
end
end
teacher_power_courses
end
def create_stages subject
if subject
subject.stages.each do |stage|
new_stage = CourseStage.create!(course_id: id, name: stage.name, description: stage.description, position: stage.position)
stage.stage_shixuns.each do |stage_shixun|
CourseStageShixun.create!(course_id: id, course_stage_id: new_stage.id, shixun_id: stage_shixun.shixun_id, position: stage_shixun.position)
end
end
end
end
def learning? user_id
Myshixun.where(user_id: user_id, shixun_id: shixuns).exists?
end
def my_subject_progress myshixuns
my_challenge_count = Game.where(myshixun_id: myshixuns.pluck(:id), status: 2).pluck(:challenge_id).uniq.size
course_challeng_count = shixuns.pluck(:challenges_count).sum
count = course_challeng_count == 0 ? 0 : ((my_challenge_count.to_f / course_challeng_count).round(2) * 100).to_i
end
# 课堂实训作业的评测次数
def evaluate_count
course_user_ids = students.pluck(:user_id)
shixun_ids = homework_commons.joins(:homework_commons_shixun).where(homework_type: 4).pluck(:shixun_id)
return 0 if shixun_ids.blank?
Game.joins(:challenge).where(challenges: {shixun_id: shixun_ids}, games: {user_id: course_user_ids}).sum(:evaluate_count)
end
def max_activity_time
course_acts.pluck(:updated_at).max
end
# 课堂作业数
def course_homework_count type
homework_commons.select{|homework| homework.homework_type == type}.size
end
private
#创建课程后,给该用户发送消息
def send_tiding
self.tidings << Tiding.new(user_id: tea_id, trigger_user_id: 0, belong_container_id: id,
belong_container_type: 'Course', tiding_type: 'System')
end
def get_name_by_type(type)
case type
when 'activity' then '动态'
when 'announcement' then '公告栏'
when 'online_learning' 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 'video' then '视频直播'
when 'board' then '讨论'
when 'course_group' then '分班'
when 'statistics' then '统计'
else ''
end
end
def get_position_by_type(type)
case type
when 'activity' then 1
when 'announcement' then 2
when 'online_learning' then 3
when 'shixun_homework' then 4
when 'common_homework' then 5
when 'group_homework' then 6
when 'graduation' then 7
when 'exercise' then 8
when 'poll' then 9
when 'attachment' then 10
when 'video' then 11
when 'board' then 12
when 'course_group' then 13
when 'statistics' then 14
else 100
end
end
def set_laboratory
return unless new_record? # 新记录才需要标记
self.laboratory = Laboratory.current if laboratory_id.blank?
end
def validate_sensitive_string
raise("课堂名称包含敏感词汇,请重新输入") unless HarmoniousDictionary.clean?(name)
end
end

View File

@@ -0,0 +1,54 @@
class CourseActivity < ApplicationRecord
belongs_to :course_act, polymorphic: true
belongs_to :course
belongs_to :user
belongs_to :exercise
belongs_to :poll
belongs_to :course_message
belongs_to :homework_common
# after_create :add_course_lead
def container_name
case course_act_type
when "HomeworkCommon"
course_act&.name
when "Exercise"
course_act&.exercise_name
when "Poll"
course_act&.poll_name
when "Message"
course_act&.subject
else
""
end
end
# 发布新课导语
# 导语要放置在课程创建信息之后
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

View File

@@ -0,0 +1,34 @@
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
has_many :teacher_course_groups, :dependent => :destroy
has_many :homework_group_settings, :dependent => :destroy
scope :by_group_ids, lambda { |ids| where(id: ids)}
validates :name, length: { maximum: 60, too_long: "不能超过60个字符" }
validates_uniqueness_of :name, scope: :course_id, message: "不能创建相同名称的分班"
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

View File

@@ -0,0 +1,3 @@
class CourseHomeworkCategory < ApplicationRecord
belongs_to :course
end

View File

@@ -0,0 +1,3 @@
class CourseInfo < ApplicationRecord
belongs_to :course
end

15
app/models/course_list.rb Normal file
View File

@@ -0,0 +1,15 @@
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
belongs_to :user
validate :validate_sensitive_string
def validate_sensitive_string
raise("课程名称包含敏感词汇,请重新输入") unless HarmoniousDictionary.clean?(name)
end
end

159
app/models/course_member.rb Normal file
View File

@@ -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.joins(:homework_common).where(user_id: self.user_id, homework_commons: {course_id: course.id})
student_works.update_all(is_delete: 0)
exercise_users = ExerciseUser.joins(:exercise).where(user_id: self.user_id, exercises: {course_id: course.id})
exercise_users.update_all(is_delete: 0)
poll_users = PollUser.joins(:poll).where(user_id: self.user_id, polls: {course_id: course.id})
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).compact
none_group_teachers = teacher_groups.pluck(:course_member_id).compact.size > 0 ? teacher_groups.pluck(:course_member_id).compact.join(',') : -1
teachers = course.teachers.where("course_members.id not in (#{none_group_teachers}) or
course_members.id in (#{member_ids.size > 0 ? member_ids.join(',') : -1})")
else
teachers = course.teachers
end
teachers
end
end

View File

@@ -0,0 +1,45 @@
class CourseMessage < ApplicationRecord
enum status: { UNHANDLED: 0, PASSED: 1, REJECTED: 2 }
belongs_to :course
belongs_to :user
has_one :course_act, class_name: 'CourseActivity', as: :course_act, dependent: :destroy
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 }
after_create :act_as_course_activity
def pass!
update!(status: :PASSED)
send_deal_tiding(1)
end
def application_user
User.find_by(id: course_message_id)
end
def reject!
update!(status: :REJECTED)
send_deal_tiding(2)
end
private
#课程动态公共表记录
def act_as_course_activity
self.course_act << CourseActivity.new(user_id: course_message_id, course_id: course_id)
end
def send_deal_tiding deal_status
# 发送申请处理结果消息
Tiding.create!(
user_id: course_message_id, trigger_user_id: 0, container_id: course_id, container_type: 'DealCourse',
belong_container: course, extra: content.to_i == 2 ? '9' : '7', tiding_type: 'System', status: deal_status
)
# 将申请消息置为已处理
Tiding.where(trigger_user_id: course_message_id, container_id: course_id, container_type: 'JoinCourse', status: 0).update_all(status: 1)
end
end

View File

@@ -0,0 +1,28 @@
class CourseModule < ApplicationRecord
default_scope { order("course_modules.position ASC") }
belongs_to :course
# 二级目录
has_many :course_second_categories
validates :module_name, length: { maximum: 20, too_long: "不能超过20个字符" }
scope :not_hidden, -> { where(hidden: 0) }
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

View File

@@ -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: 60, too_long: "不能超过60个字符" }
def category_type_str
category_type == "graduation" && name == "毕设选题" ? "graduation_topics" : (
category_type == "graduation" && name == "毕设任务" ? "graduation_tasks" : category_type
)
end
end

View File

@@ -0,0 +1,9 @@
class CourseStage < ApplicationRecord
belongs_to :course
has_many :course_stage_shixuns, -> { order("course_stage_shixuns.position ASC") }, dependent: :destroy
has_many :shixuns, :through => :course_stage_shixuns
validates :name, length: { maximum: 60 , too_long: "不能超过60个字符"}
validates :description, length: { maximum: 1000, too_long: "不能超过1000个字符" }
end

View File

@@ -0,0 +1,5 @@
class CourseStageShixun < ApplicationRecord
belongs_to :course
belongs_to :course_stage, counter_cache: :shixuns_count
belongs_to :shixun
end

View File

@@ -0,0 +1,4 @@
class CourseVideo < ApplicationRecord
belongs_to :course
belongs_to :video
end

11
app/models/customer.rb Normal file
View File

@@ -0,0 +1,11 @@
class Customer < ApplicationRecord
default_scope { order(created_at: :desc) }
belongs_to :school
belongs_to :partner_manager_group, optional: true
has_many :partner_customers, dependent: :destroy
has_many :partners, through: :partner_customers
has_many :users
end

19
app/models/department.rb Normal file
View File

@@ -0,0 +1,19 @@
class Department < ApplicationRecord
belongs_to :school
has_many :department_members, dependent: :destroy
has_many :member_users, through: :department_members, source: :user
has_many :user_extensions, dependent: :nullify
has_many :apply_add_departments, dependent: :destroy
scope :without_deleted, -> { where(is_delete: false) }
def member?(user)
department_members.exists?(user_id: user.id)
end
def soft_delete!
update!(is_delete: true)
end
end

View File

@@ -0,0 +1,4 @@
class DepartmentMember < ApplicationRecord
belongs_to :user
belongs_to :department
end

View File

@@ -0,0 +1,8 @@
class DiffRecord < ApplicationRecord
belongs_to :user
belongs_to :container, polymorphic: true
has_one :diff_record_content, dependent: :destroy
delegate :content, to: :diff_record_content
end

View File

@@ -0,0 +1,3 @@
class DiffRecordContent < ApplicationRecord
belongs_to :diff_record
end

12
app/models/discipline.rb Normal file
View File

@@ -0,0 +1,12 @@
class Discipline < ApplicationRecord
default_scope { order(position: :asc) }
has_many :sub_disciplines, -> { order("sub_disciplines.position ASC") }, dependent: :destroy
has_many :shixun_sub_disciplines, -> { where("shixun = 1") }, class_name: "SubDiscipline"
has_many :subject_sub_disciplines, -> { where("subject = 1") }, class_name: "SubDiscipline"
has_many :question_sub_disciplines, -> { where("question = 1") }, class_name: "SubDiscipline"
validates_presence_of :name
end

80
app/models/discuss.rb Normal file
View File

@@ -0,0 +1,80 @@
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_treads, as: :praise_tread_object, dependent: :destroy
has_many :tidings, as: :container, dependent: :destroy
has_one :praise_tread_cache, as: :object, dependent: :destroy
belongs_to :dis, polymorphic: true
belongs_to :challenge, optional: true
validate :validate_sensitive_string
validates :content, length: { maximum: 2000, too_long: "不能超过2000个字符" }
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
def child_discuss(user)
Discuss.where(parent_id: self.id).includes(:user).reorder(created_at: :asc)
end
def child_discuss_count
Discuss.where(root_id: id).count
end
private
def send_tiding
if dis_type == 'Shixun'
send_user_id = has_parent? ? parent.user_id : Challenge.find(challenge_id).user_id
parent_container_type = 'Challenge'
challenge_id = challenge_id
extra = ''
elsif dis_type == 'Hack'
send_user_id = has_parent? ? parent.user_id : Hack.find(dis_id).user_id
parent_container_type = 'Hack'
challenge_id = dis_id
extra = HackUserLastestCode.where(user_id: user_id, hack_id: dis_id).first&.identifier
end
base_attrs = {
trigger_user_id: user_id, parent_container_id: challenge_id, parent_container_type: parent_container_type,
belong_container_id: dis_id, belong_container_type: dis_type, viewed: 0, tiding_type: 'Comment', extra: extra
}
tidings.create!(base_attrs.merge(user_id: send_user_id))
end
def validate_sensitive_string
raise("内容包含敏感词汇,请重新输入") unless HarmoniousDictionary.clean?(content)
end
end

25
app/models/edu_setting.rb Normal file
View File

@@ -0,0 +1,25 @@
class EduSetting < ApplicationRecord
after_commit :expire_value_cache
def value_cache_key
self.class.value_cache_key(name)
end
def self.get(key)
Rails.cache.fetch(value_cache_key(key), expires_in: 1.days) do
find_by_name(key.to_s)&.value
end
end
def self.value_cache_key(name)
raise ArgumentError if name.blank?
"educoder/edu-settings/#{name.to_s}"
end
private
def expire_value_cache
Rails.cache.write(value_cache_key, value)
end
end

3
app/models/experience.rb Normal file
View File

@@ -0,0 +1,3 @@
class Experience < ApplicationRecord
belongs_to :user
end

View File

@@ -0,0 +1,5 @@
class ForgeActivity < ApplicationRecord
belongs_to :user
belongs_to :project
belongs_to :forge_act, polymorphic: true
end

3
app/models/forum.rb Normal file
View File

@@ -0,0 +1,3 @@
class Forum < ApplicationRecord
has_many :memos, dependent: :destroy
end

3
app/models/help.rb Normal file
View File

@@ -0,0 +1,3 @@
class Help < ApplicationRecord
end

3
app/models/ignore.rb Normal file
View File

@@ -0,0 +1,3 @@
class Ignore < ApplicationRecord
include Projectable
end

104
app/models/issue.rb Normal file
View File

@@ -0,0 +1,104 @@
class Issue < ApplicationRecord
#issue_type 1为普通2为悬赏
belongs_to :project, :counter_cache => true
belongs_to :tracker,optional: true
has_many :project_trends, as: :trend, dependent: :destroy
has_one :pull_request
# belongs_to :issue_tag,optional: true
belongs_to :priority, :class_name => 'IssuePriority', foreign_key: :priority_id,optional: true
belongs_to :version, foreign_key: :fixed_version_id,optional: true, counter_cache: true
belongs_to :user,optional: true, foreign_key: :author_id
belongs_to :issue_status, foreign_key: :status_id,optional: true
has_many :commit_issues
has_many :attachments, as: :container, dependent: :destroy
has_many :memos
has_many :journals, :as => :journalized, :dependent => :destroy
has_many :journal_details, through: :journals
has_many :issue_tags_relates, dependent: :destroy
has_many :issue_tags, through: :issue_tags_relates
has_many :issue_times, dependent: :destroy
has_many :issue_depends, dependent: :destroy
scope :issue_includes, ->{includes(:user)}
scope :issue_many_includes, ->{includes(journals: :user)}
scope :issue_issue, ->{where(issue_classify: [nil,"issue"])}
scope :issue_pull_request, ->{where(issue_classify: "pull_request")}
after_update :change_versions_count
def get_assign_user
User.select(:login, :lastname,:firstname, :nickname)&.find_by_id(self.assigned_to_id)
end
def create_journal_detail(change_files, issue_files, issue_file_ids)
journal_params = {
journalized_id: self.id, journalized_type: "Issue", user_id: self.author_id
}
journal = Journal.new journal_params
if journal.save
if change_files
old_attachment_names = self.attachments.select(:filename,:id).where(id: issue_file_ids).pluck(:filename).join(",")
new_attachment_name = self.attachments.select(:filename,:id).where(id: issue_files).pluck(:filename).join(",")
journal.journal_details.create(property: "attachment", prop_key: "#{issue_files.size}", old_value: old_attachment_names, value: new_attachment_name)
end
change_values = %w(subject description is_private assigned_to_id tracker_id status_id priority_id fixed_version_id start_date due_date estimated_hours done_ratio issue_tags_value issue_type token)
change_values.each do |at|
if self.send("saved_change_to_#{at}?")
journal.journal_details.create(property: "attr", prop_key: "#{at}", old_value: self.send("#{at}_before_last_save"), value: self.send(at))
end
end
end
end
def custom_journal_detail(prop_key, old_value, value)
journal_params = {
journalized_id: self.id, journalized_type: "Issue", user_id: self.author_id
}
journal = Journal.new journal_params
if journal.save
journal.journal_details.create(property: "attr", prop_key: prop_key, old_value: old_value, value: value)
end
end
def get_journals_size
journals.size
end
def self.issues_count(tracker_id)
includes(:trakcer).where(tracker_id: tracker_id).size
end
def get_issue_tags
if issue_tags.present?
issue_tags.select(:id,:name,:color).uniq.as_json
else
nil
end
end
def get_issue_tags_name
if issue_tags.present?
issue_tags.select(:name).uniq.pluck(:name).join(",")
else
nil
end
end
def only_reply_journals
journals.where.not(notes: [nil, ""]).journal_includes.limit(2)
end
def change_versions_count
if self.version.present?
if self.status_id == 5
percent = self.version.issues_count == 0 ? 0.0 : ((self.version.closed_issues_count + 1).to_f / self.version.issues_count)
self.version.update_attributes(closed_issues_count: (self.version.closed_issues_count + 1), percent: percent)
else
percent = self.version.issues_count == 0 ? 0.0 : ((self.version.closed_issues_count - 1).to_f / self.version.issues_count)
self.version.update_attributes(closed_issues_count: (self.version.closed_issues_count - 1), percent: percent)
end
end
end
end

View File

@@ -0,0 +1,3 @@
class IssueDepend < ApplicationRecord
belongs_to :issue
end

View File

@@ -0,0 +1,3 @@
class IssuePriority < ApplicationRecord
has_many :issues
end

View File

@@ -0,0 +1,4 @@
class IssueStatus < ApplicationRecord
has_many :issues
belongs_to :project, optional: true
end

7
app/models/issue_tag.rb Normal file
View File

@@ -0,0 +1,7 @@
class IssueTag < ApplicationRecord
has_many :issue_tags_relates, dependent: :destroy
has_many :issues, through: :issue_tags_relates
belongs_to :project, optional: true
end

View File

@@ -0,0 +1,4 @@
class IssueTagsRelate < ApplicationRecord
belongs_to :issue
belongs_to :issue_tag, counter_cache: :issues_count
end

4
app/models/issue_time.rb Normal file
View File

@@ -0,0 +1,4 @@
class IssueTime < ApplicationRecord
belongs_to :issue
belongs_to :user
end

View File

@@ -0,0 +1,4 @@
class ItemAnalysis < ApplicationRecord
belongs_to :item_bank, touch: true
validates :analysis, length: { maximum: 5000, too_long: "不能超过5000个字符" }
end

45
app/models/item_bank.rb Normal file
View File

@@ -0,0 +1,45 @@
class ItemBank < ApplicationRecord
# difficulty: 1 简单 2 适中 3 困难
enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 }
# item_type: 0 单选 1 多选 2 判断 3 填空 4 简答 5 实训 6 编程
belongs_to :user
belongs_to :sub_discipline, optional: true
has_one :item_analysis, dependent: :destroy
has_many :item_choices, dependent: :destroy
has_many :item_baskets, dependent: :destroy
has_many :tag_discipline_containers, as: :container, dependent: :destroy
has_many :tag_disciplines, through: :tag_discipline_containers
belongs_to :container, polymorphic: true, optional: true
validates :name, presence: true, length: { maximum: 1000, too_long: "不能超过1000个字符" }
def analysis
item_analysis&.analysis
end
def apply?
!public && ApplyAction.exists?(container_type: "ItemBank", container_id: id, status: 0)
end
def type_string
case item_type
when "SINGLE" then "单选题"
when "MULTIPLE" then "多选题"
when "JUDGMENT" then "判断题"
when "COMPLETION" then "填空题"
when "SUBJECTIVE" then "简答题"
when "PRACTICAL" then "实训题"
when "PROGRAM" then "编程题"
end
end
def difficulty_string
case difficulty
when 1 then "简单"
when 2 then "适中"
when 3 then "困难"
end
end
end

View File

@@ -0,0 +1,7 @@
class ItemBasket < ApplicationRecord
enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 }
belongs_to :item_bank
belongs_to :user, optional: true
belongs_to :examination_intelligent_setting, optional: true
end

View File

@@ -0,0 +1,5 @@
class ItemChoice < ApplicationRecord
belongs_to :item_bank, touch: true
validates :choice_text, presence: true, length: { maximum: 500, too_long: "不能超过500个字符" }
end

150
app/models/journal.rb Normal file
View File

@@ -0,0 +1,150 @@
class Journal < ApplicationRecord
belongs_to :user
belongs_to :issue, foreign_key: :journalized_id, :touch => true
has_many :journal_details, :dependent => :delete_all
has_many :attachments, as: :container, dependent: :destroy
scope :journal_includes, ->{includes(:user, :journal_details, :attachments)}
scope :parent_journals, ->{where(parent_id: nil)}
scope :children_journals, lambda{|journal_id| where(parent_id: journal_id)}
def is_journal_detail?
self.notes.blank? && self.journal_details.present?
end
def journal_content
send_details = []
if self.is_journal_detail?
details = self.journal_details.select(:property, :prop_key, :old_value, :value).pluck(:property, :prop_key, :old_value, :value)
if details.size > 0
details.each do |de|
if de[0] == "attr"
content = ""
else
content = "附件"
end
old_value = de[2]
value = de[3]
if de[1].to_i > 0
prop_name = ""
else
prop_name = I18n.t("journal_detail.#{de[1]}")
case de[1]
when "is_private"
old_value = I18n.t("journal_detail.#{de[2]}")
value = I18n.t("journal_detail.#{de[3]}")
when "assigned_to_id"
u = User.select(:id, :login, :lastname, :firstname, :nickname)
old_value = de[2].to_i > 0 ? u.find_by_id(de[2]).try(:show_real_name) : ""
assign_user = de[3].to_i > 0 ? u.find_by_id(de[3]) : ""
if assign_user.present?
value = assign_user.try(:show_real_name)
else
value = ""
end
when "tracker_id"
t = Tracker.select(:id, :name)
old_value = de[2].to_i > 0 ? t.find_by_id(de[2]).try(:name) : ""
tracker_name = de[3].to_i > 0 ? t.find_by_id(de[3]) : ""
if tracker_name
value = tracker_name.try(:name)
else
value = ""
end
when "status_id"
t = IssueStatus.select(:id, :name)
old_value = de[2].to_i > 0 ? t.find_by_id(de[2]).try(:name) : ""
type_name = de[3].to_i > 0 ? t.find_by_id(de[3]) : ""
if type_name
value = type_name.try(:name)
else
value = ""
end
when "priority_id"
t = IssuePriority.select(:id, :name)
old_value = de[2].to_i > 0 ? t.find_by_id(de[2]).try(:name): ""
type_name = de[3].to_i > 0 ? t.find_by_id(de[3]) : ""
if type_name
value = type_name.try(:name)
else
value = ""
end
when "issue_tags_value"
t = IssueTag.select(:id, :name)
old_value = de[2].to_i > 0 ? t.where(id: de[2].split(",")).select(:id,:name,:color).as_json : ""
if de[3].present?
value = t.where(id: de[3].split(",")).select(:id,:name,:color).as_json
else
value = ""
end
when "fixed_version_id"
t = Version.select(:id, :name)
old_value = de[2].to_i > 0 ? t.find_by_id(de[2]).try(:name) : ""
type_name = de[3].to_i > 0 ? t.find_by_id(de[3]) : ""
if type_name
value = type_name.try(:name)
else
value = ""
end
when "end_time"
t = IssueTime.select(:id, :start_time, :end_time)
type_name = de[2].to_i > 0 ? t.find_by_id(de[2]) : ""
if type_name.present?
old_value = "停止工作"
d_value = type_name.end_time.to_i - type_name.start_time.to_i
value = "#{Time.at(d_value).utc.strftime('%H h %M min %S s')}"
else
old_value = "停止工作"
value = ""
end
when "issue_depend"
t = Issue.select(:id,:subject )
type_name = de[3].present? ? t&.find_by_id(de[3]) : ""
if type_name.present?
old_value = "增加依赖"
value = {
id: de[3],
name: type_name.try(:subject)
}
else
old_value = "增加依赖"
value = ""
end
when "destroy_issue_depend"
t = Issue.select(:id,:subject )
type_name = de[3].present? ? t&.find_by_id(de[3]) : ""
if type_name.present?
old_value = "删除依赖"
value = {
id: de[3],
name: type_name.try(:subject)
}
else
old_value = "删除依赖"
value = ""
end
when "done_ratio"
old_value = "#{de[2]}%"
value = "#{de[3]}%"
else
old_value = de[2]
value = de[3]
end
end
prop_hash = {
detail: (content + prop_name),
old_value: old_value,
value: value
}
send_details.push(prop_hash)
end
end
end
send_details
end
end

View File

@@ -0,0 +1,3 @@
class JournalDetail < ApplicationRecord
belongs_to :journal
end

View File

@@ -0,0 +1,80 @@
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)}
has_many :tidings, as: :container, dependent: :destroy
# "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", 隐藏、
validates :notes, length: { maximum: 2000, too_long: "不能超过2000个字符" }
after_create :send_tiding
# 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
def send_tiding
# 回复和@同一个人时:只发@的消息(因@的消息先创建)
case self.jour_type
# 用户留言当做私信处理 不发消息
when "Principal"
=begin
user_id = self.m_parent_id.present? ? JournalsForMessage.find(self.m_parent_id).user_id : self.jour_id
if user_id != self.user_id && !self.tidings.where(:user_id => user_id, :trigger_user_id => self.user_id, :tiding_type => "Mentioned").first.present?
self.tidings << Tiding.new(:trigger_user_id => self.user_id, :user_id => user_id, :parent_container_id => self.jour_id, :parent_container_type => self.jour_type, :belong_container_id => self.jour_id, :belong_container_type => "User", :viewed => 0, :tiding_type => self.m_parent_id.present? ? "Comment" : "Journal")
end
=end
when "HomeworkCommon", "GraduationTopic"
user_id = self.m_parent_id.present? ? JournalsForMessage.find(self.m_parent_id).user_id : (self.jour_type == "HomeworkCommon" ? self.jour.user_id : self.jour.tea_id)
if user_id != self.user_id && !self.tidings.where(:user_id => user_id, :trigger_user_id => self.user_id, :tiding_type => "Mentioned").first.present?
self.tidings << Tiding.new(:trigger_user_id => self.user_id, :user_id => user_id, :parent_container_id => self.jour_id, :parent_container_type => self.jour_type, :belong_container_id => self.jour.course_id, :belong_container_type => "Course", :viewed => 0, :tiding_type => "Comment")
end
when "StudentWorksScore"
course_id = self.jour.try(:student_work).try(:homework_common).try(:course_id)
user_id = self.m_parent_id.present? ? JournalsForMessage.find(self.m_parent_id).user_id : self.jour.user_id
if user_id != self.user_id && !self.tidings.where(:user_id => user_id, :trigger_user_id => self.user_id, :tiding_type => "Mentioned").first.present?
self.tidings << Tiding.new(:trigger_user_id => self.user_id, :user_id => user_id, :parent_container_id => self.jour_id, :parent_container_type => self.jour_type, :belong_container_id => course_id, :belong_container_type => "Course", :viewed => 0, :tiding_type => "Comment")
end
end
end
end

129
app/models/laboratory.rb Normal file
View File

@@ -0,0 +1,129 @@
class Laboratory < ApplicationRecord
belongs_to :school, optional: true
has_many :laboratory_users, dependent: :destroy
has_many :users, through: :laboratory_users, source: :user
has_one :laboratory_setting, dependent: :destroy
has_many :portal_images, dependent: :destroy
has_many :laboratory_shixuns, dependent: :destroy
# has_many :shixuns, through: :laboratory_shixuns, source: :shixun
has_many :laboratory_subjects, dependent: :destroy
# has_many :subjects, through: :laboratory_subjects, source: :subject
has_many :courses, dependent: :destroy
has_many :competitions, dependent: :destroy
has_many :libraries, dependent: :destroy
validates :identifier, uniqueness: { case_sensitive: false }, allow_nil: true
delegate :name, :navbar, :footer, :login_logo_url, :nav_logo_url, :tab_logo_url, :default_navbar, to: :laboratory_setting
def site
rails_env = EduSetting.get('rails_env')
suffix = rails_env && rails_env != 'production' ? ".#{rails_env}.educoder.net" : '.educoder.net'
identifier ? "#{identifier}#{suffix}" : ''
end
def self.find_by_subdomain(subdomain)
return if subdomain.blank?
rails_env = EduSetting.get('rails_env')
subdomain = subdomain.slice(0, subdomain.size - rails_env.size - 1) if rails_env && subdomain.end_with?(rails_env) # winse.dev => winse
find_by_identifier(subdomain)
end
# def self.current=(laboratory)
# Thread.current[:current_laboratory] = laboratory
# end
#
# def self.current
# Thread.current[:current_laboratory] ||= Laboratory.find(1)
# end
def self.current=(user)
RequestStore.store[:current_laboratory] = user
end
def self.current
RequestStore.store[:current_laboratory] ||= User.anonymous
end
def shixuns
if main_site?
not_shixun_ids = Shixun.joins(:laboratory_shixuns).where("laboratory_shixuns.laboratory_id != #{Laboratory.current.id}")
Shixun.where.not(id: not_shixun_ids.pluck(:shixun_id))
elsif sync_shixun
laboratory_shixun_ids = laboratory_shixuns.pluck(:shixun_id)
school_shixun_ids = Shixun.joins("join user_extensions on shixuns.user_id=user_extensions.user_id").where(user_extensions: { school_id: school_id }).pluck(:id)
shixun_ids = laboratory_shixun_ids + school_shixun_ids
Shixun.where(id: shixun_ids.uniq)
else
Shixun.joins(:laboratory_shixuns).where(laboratory_shixuns: { laboratory_id: id })
end
end
def subjects
if main_site?
not_subject_ids = Subject.joins(:laboratory_subjects).where("laboratory_subjects.laboratory_id != #{Laboratory.current.id}")
Subject.where.not(id: not_subject_ids.pluck(:subject_id))
elsif sync_subject
laboratory_subject_ids = laboratory_subjects.pluck(:subject_id)
school_subject_ids = Subject.joins("join user_extensions on subjects.user_id=user_extensions.user_id").where(user_extensions: { school_id: school_id }).pluck(:id)
subject_ids = laboratory_subject_ids + school_subject_ids
Subject.where(id: subject_ids.uniq)
else
Subject.joins(:laboratory_subjects).where(laboratory_subjects: { laboratory_id: id })
end
end
def all_courses
main_site? || !sync_course ? courses : courses.or(Course.where(school_id: school_id))
end
def shixun_repertoires
where_sql = ShixunTagRepertoire.where("shixun_tag_repertoires.tag_repertoire_id = tag_repertoires.id")
# 云上实验室过滤
unless main_site?
where_sql = where_sql.joins("JOIN laboratory_shixuns ls ON ls.shixun_id = shixun_tag_repertoires.shixun_id "\
"AND ls.laboratory_id = #{id}")
end
where_sql = where_sql.select('1').to_sql
tags = TagRepertoire.where("EXISTS(#{where_sql})").distinct.includes(sub_repertoire: :repertoire)
tags_map = tags.group_by(&:sub_repertoire)
sub_reps_map = tags_map.keys.group_by(&:repertoire)
sub_reps_map.keys.sort_by(&:updated_at).reverse.map do |repertoire|
repertoire_hash = repertoire.as_json(only: %i[id name])
repertoire_hash[:sub_repertoires] =
sub_reps_map[repertoire].sort_by(&:updated_at).reverse.map do |sub_repertoire|
sub_repertoire_hash = sub_repertoire.as_json(only: %i[id name])
sub_repertoire_hash[:tags] = tags_map[sub_repertoire].sort_by(&:updated_at).reverse.map { |tag| tag.as_json(only: %i[id name]) }
sub_repertoire_hash
end
repertoire_hash
end
end
def subject_repertoires
exist_sql = Subject.where('subjects.repertoire_id = repertoires.id')
unless main_site?
exist_sql = exist_sql.joins(:laboratory_subjects).where(laboratory_subjects: { laboratory_id: id })
end
Repertoire.where("EXISTS(#{exist_sql.select('1').to_sql})").order(updated_at: :desc).distinct
end
# 是否为主站
def main_site?
id == 1
end
end

View File

@@ -0,0 +1,75 @@
class LaboratorySetting < ApplicationRecord
belongs_to :laboratory
serialize :config, JSON
%i[name navbar footer].each do |method_name|
define_method method_name do
config&.[](method_name.to_s)
end
define_method "#{method_name}=" do |value|
self.config ||= {}
config.[]=(method_name.to_s, value)
end
end
def login_logo_url
image_url('login')
end
def nav_logo_url
image_url('nav')
end
def tab_logo_url
image_url('tab')
end
def subject_banner_url
image_url('_subject_banner')
end
def course_banner_url
image_url('_course_banner')
end
def competition_banner_url
image_url('_competition_banner')
end
def moop_cases_banner_url
image_url('_moop_cases_banner')
end
def oj_banner_url
image_url('_oj_banner')
end
def default_navbar
self.class.default_config[:navbar]
end
private
def image_url(type)
return nil unless Util::FileManage.exists?(self, type)
Util::FileManage.source_disk_file_url(self, type)
end
def self.default_config
{
name: nil,
navbar: [
{ 'name' => '实践课程', 'link' => '/paths', 'hidden' => false },
{ 'name' => '翻转课堂', 'link' => '/courses', 'hidden' => false },
{ 'name' => '实训项目', 'link' => '/shixuns', 'hidden' => false },
{ 'name' => '在线竞赛', 'link' => '/competitions', 'hidden' => false },
{ 'name' => '教学案例', 'link' => '/moop_cases', 'hidden' => false },
{ 'name' => '交流问答', 'link' => '/forums', 'hidden' => false },
{ 'name' => '开发者社区', 'link' => '/problems', 'hidden' => false },
],
footer: nil
}
end
end

View File

@@ -0,0 +1,4 @@
class LaboratoryUser < ApplicationRecord
belongs_to :laboratory
belongs_to :user
end

3
app/models/license.rb Normal file
View File

@@ -0,0 +1,3 @@
class License < ApplicationRecord
include Projectable
end

21
app/models/live_link.rb Normal file
View File

@@ -0,0 +1,21 @@
class LiveLink < ApplicationRecord
belongs_to :course
belongs_to :user
has_many :tidings, as: :container, dependent: :destroy
# validates :url, format: { with: CustomRegexp::URL, message: "必须为网址超链接" }
validates :description, length: { maximum: 100, too_long: "不能超过100个字符" }
validates :course_name, presence: true
validates :platform, presence: true
# validates :live_time, presence: true
validates :duration, numericality: { only_integer: true, greater_than: 0}, allow_blank: true
def op_auth?
user == User.current || User.current.admin_or_business?
end
def delete_auth?
user == User.current || User.current.admin?
end
end

11
app/models/member.rb Normal file
View File

@@ -0,0 +1,11 @@
class Member < ApplicationRecord
belongs_to :user
belongs_to :course, optional: true
belongs_to :project, optional: true
has_many :member_roles, dependent: :destroy
has_many :roles, through: :member_roles
validates :user_id, :project_id, presence: true
end

View File

@@ -0,0 +1,6 @@
class MemberRole < ApplicationRecord
belongs_to :role
belongs_to :member
validates :member_id, :role_id, presence: true
end

72
app/models/memo.rb Normal file
View File

@@ -0,0 +1,72 @@
class Memo < ApplicationRecord
include Searchable::Memo
belongs_to :forum, touch: true
has_many :memo_tag_repertoires, dependent: :destroy
has_many :tag_repertoires, :through => :memo_tag_repertoires
has_many :praise_treads, 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'
has_many :descendants, foreign_key: :root_id, class_name: 'Memo'
has_many :children, foreign_key: :parent_id, class_name: 'Memo'
has_many :attachments, as: :container, dependent: :destroy
has_many :tidings, as: :container, dependent: :destroy
validate :validate_sensitive_string
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]) }
validates :content, length: { maximum: 10000, too_long: "不能超过10000个字符" }
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
# 主贴的名称
def main_subject
memo = Memo.find_by(root_id: id)
Rails.logger.info("###############memo: #{memo&.subject}")
memo ? memo.subject : subject
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
def validate_sensitive_string
raise("标题包含敏感词汇,请重新输入") unless HarmoniousDictionary.clean?(subject)
raise("内容包含敏感词汇,请重新输入") unless HarmoniousDictionary.clean?(content)
end
end

View File

@@ -0,0 +1,4 @@
class MemoTagRepertoire < ApplicationRecord
belongs_to :memo
belongs_to :tag_repertoire
end

102
app/models/message.rb Normal file
View File

@@ -0,0 +1,102 @@
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
belongs_to :root, class_name: 'Message', foreign_key: :root_id, counter_cache: :descendants_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 # 课程动态
has_many :descendants, class_name: 'Message', foreign_key: :root_id, 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, too_long: "不能超过255个字符" }
def update_content(content)
message_detail.update_attributes(content: content)
end
# 主贴的名称
def main_subject
Rails.logger.info("##########parent: #{parent&.subject}")
parent.present? ? parent.subject : subject
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)
to_board = Board.find(to_board_id)
messages = Message.where(id: message_ids, parent_id: nil).select(:id, :board_id).to_a
return if messages.blank?
from_board = Board.find(messages.first.board_id)
root_ids = messages.map(&:id)
children_ids = Message.where(parent_id: root_ids).pluck(:id)
second_children_ids = Message.where(parent_id: children_ids).pluck(:id)
ids = root_ids.concat(children_ids).concat(second_children_ids).uniq
ActiveRecord::Base.transaction do
Message.where(id: ids, board_id: from_board.id).update_all(board_id: to_board.id)
to_board.increment!(:messages_count, ids.size)
from_board.increment!(:messages_count, - ids.size)
end
end
# 包含二级回复的总点赞数
def total_praises_count
praises_count + descendants.sum(:praises_count)
end
# 包含二级回复数的总回复数
def total_replies_count
descendants_count
end
def has_replies
children.exists?
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

View File

@@ -0,0 +1,5 @@
class MessageDetail < ApplicationRecord
belongs_to :message, :touch => true
validates :content, length: { maximum: 10000, too_long: "内容不能超过10000个字符" }
end

View File

@@ -0,0 +1,7 @@
# status: 0 创建镜像; 1 修改镜像ID 2 修改镜像name 3 删除镜像 4.从主节点同步镜像到子节点(子节点发生异常), 5. 修改镜像别名, 6. 修改镜像的状态
# user_id: -1时证明是非人为因素造成中间层异常导致
class MirrorOperationRecord < ActiveRecord::Base
default_scope { order(created_at: :desc) }
belongs_to :mirror_repository
end

View File

@@ -0,0 +1,16 @@
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,2,3,5]) }
scope :published_main_mirror, -> { published_mirror.where(main_type: 1) }
scope :published_small_mirror, -> { published_mirror.where(main_type: 0) }
scope :small_mirror, -> { where(main_type: 0) }
def deletable?
status != 1 && !shixun_mirror_repositories.exists?
end
end

View File

@@ -0,0 +1,4 @@
class MirrorScript < ApplicationRecord
belongs_to :mirror_repository
end

View File

@@ -0,0 +1,2 @@
class ModuleSetting < ApplicationRecord
end

118
app/models/myshixun.rb Normal file
View File

@@ -0,0 +1,118 @@
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 :user_extension, foreign_key: :user_id
belongs_to :shixun, counter_cache: true
has_one :last_executable_task, -> { where(status: [0, 1]).reorder(created_at: :asc) }, class_name: 'Game'
has_one :last_task, -> { all }, class_name: 'Game'
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.map(&:evaluate_count).sum.to_i
end
def repo_path
"#{self.repo_name}.git"
end
def repo_save_path
self.repo_name.split('/').last
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 可以测评的; 1 正在测评的; 2评测通过的 3未开启的
# 如果都完成,则当前任务为最后一个任务
def current_task games
current_game = games.select{|game| game.status == 1 || game.status == 0}.last
if current_game.blank?
current_game = games.last
end
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.select{|game| game.status == 2 && game.final_score > 0}.pluck(:final_score).sum.to_i
end
# 个人通关数
def passed_count
self.games.select{|game| game.status == 2}.size
end
# 指定时间前完成的关卡数
def time_passed_count time
time.present? ? self.games.select{|game| game.status == 2 && game.end_time < time}.size : 0
end
# 查看答案的关卡数,只统计通关前看的关卡
def view_answer_count
answer_ids = user.grades.joins("join games on grades.container_id = games.id").where("container_type = 'Answer' and games.status=2 and games.end_time > grades.created_at").pluck(:container_id)
self.games.select{|game| game.status == 2 && game.answer_open != 0 && answer_ids.include?(game.id)}.size
end
# 通关时间
def passed_time
self.status == 1 ? self.games.select{|game| game.status == 2}.map(&:end_time).max : "--"
end
# 耗时
def total_spend_time
game_spend_time total_cost_time
end
# 通关总耗时
def total_cost_time
self.games.select{|game| game.status == 2}.map(&:cost_time).sum.to_i
end
end

View File

@@ -0,0 +1,2 @@
class OldMessageDetail < ApplicationRecord
end

View File

@@ -0,0 +1,4 @@
class OnclickTime < ApplicationRecord
belongs_to :user
end

11
app/models/open_user.rb Normal file
View File

@@ -0,0 +1,11 @@
class OpenUser < ApplicationRecord
belongs_to :user
validates :uid, presence: true, uniqueness: { scope: :type }
serialize :extra, JSON
def can_bind_cache_key
"open_user:#{type}:#{uid}:can_bind"
end
end

View File

@@ -0,0 +1,9 @@
class OpenUsers::Cas < OpenUser
def nickname
extra&.[]('nickname')
end
def en_type
'cas'
end
end

View File

@@ -0,0 +1,9 @@
class OpenUsers::QQ < OpenUser
def nickname
extra&.[]('nickname')
end
def en_type
'qq'
end
end

Some files were not shown because too many files have changed in this diff Show More