first commit

dev_course
jingquan huang 6 years ago
commit b1069b9afd

56
.gitignore vendored

@ -0,0 +1,56 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# mac
*.DS_Store
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore uploaded files in development
/storage/*
/node_modules
/yarn-error.log
# /public/assets
.byebug_history
# Ignore master key for decrypting credentials and more.
/config/master.key
/config/database.yml
/.idea/*
# Ignore react node_modules
/public/react/build
/public/react/build/
/public/react/node_modules/
/public/react/config/stats.json
/public/react/stats.json
/public/npm-debug.log
# avatars
/public/images/avatars
/config/secrets.yml
/files/archiveZip/*
/files/cache_store/*
public/upload.html
/config/configuration.yml
/config/initializers/gitlab_config.rb
/db/schema.rb
.vscode/
vendor/bundle/
.ruby-version
.ruby-gemset

@ -0,0 +1,86 @@
source 'https://gems.ruby-china.com'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.3.7'
gem 'rails', '~> 5.2.0'
gem 'mysql2', '>= 0.4.4', '< 0.6.0'
gem 'puma', '~> 3.11'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
# gem 'coffee-rails', '~> 4.2'
# gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5'
gem 'grape-entity', '~> 0.7.1'
gem 'kaminari', '~> 1.1', '>= 1.1.1'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'gitlab', path: 'lib/gitlab-cli'
gem 'rack-cors'
gem 'redis-rails'
gem 'roo-xls'
gem 'simple_xlsx_reader'
gem 'rubyzip'
gem 'spreadsheet'
gem 'ruby-ole'
# 导出为xlsx
gem 'axlsx', '~> 3.0.0.pre'
gem 'axlsx_rails', '~> 0.5.2'
gem 'oauth2'
#导出为pdf
gem 'pdfkit'
gem 'wkhtmltopdf-binary'
#gem 'iconv'
gem 'rqrcode', '~> 0.10.1'
gem 'rqrcode_png'
gem 'acts-as-taggable-on', '~> 6.0'
group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'rspec-rails', '~> 3.8'
end
group :development do
gem 'awesome_print'
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
group :test do
gem 'capybara', '>= 2.15', '< 4.0'
gem 'selenium-webdriver'
gem 'chromedriver-helper'
end
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
#编码检测
gem 'rchardet', '~> 1.8'
# http client
gem 'faraday', '~> 0.15.4'
# view
gem 'active_decorator'
# i18n
gem 'rails-i18n', '~> 5.1'
# job
gem 'sidekiq'
# batch insert
gem 'bulk_insert'

@ -0,0 +1,354 @@
PATH
remote: lib/gitlab-cli
specs:
gitlab (3.2.0)
httparty
terminal-table
GEM
remote: https://gems.ruby-china.com/
specs:
actioncable (5.2.1)
actionpack (= 5.2.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailer (5.2.1)
actionpack (= 5.2.1)
actionview (= 5.2.1)
activejob (= 5.2.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.2.1)
actionview (= 5.2.1)
activesupport (= 5.2.1)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.1)
activesupport (= 5.2.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_decorator (1.2.0)
activejob (5.2.1)
activesupport (= 5.2.1)
globalid (>= 0.3.6)
activemodel (5.2.1)
activesupport (= 5.2.1)
activerecord (5.2.1)
activemodel (= 5.2.1)
activesupport (= 5.2.1)
arel (>= 9.0)
activestorage (5.2.1)
actionpack (= 5.2.1)
activerecord (= 5.2.1)
marcel (~> 0.3.1)
activesupport (5.2.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
acts-as-taggable-on (6.0.0)
activerecord (~> 5.0)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
archive-zip (0.11.0)
io-like (~> 0.3.0)
arel (9.0.0)
awesome_print (1.8.0)
axlsx (3.0.0.pre)
htmlentities (~> 4.3, >= 4.3.4)
mimemagic (~> 0.3)
nokogiri (~> 1.8, >= 1.8.2)
rubyzip (~> 1.2, >= 1.2.1)
axlsx_rails (0.5.2)
actionpack (>= 3.1)
axlsx (>= 2.0.1)
bindex (0.5.0)
bootsnap (1.3.1)
msgpack (~> 1.0)
builder (3.2.3)
bulk_insert (1.7.0)
activerecord (>= 3.2.0)
byebug (10.0.2)
capybara (3.5.1)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
xpath (~> 3.1)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
chromedriver-helper (1.2.0)
archive-zip (~> 0.10)
nokogiri (~> 1.8)
chunky_png (1.3.10)
concurrent-ruby (1.0.5)
connection_pool (2.2.2)
crass (1.0.4)
diff-lcs (1.3)
erubi (1.7.1)
execjs (2.7.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
ffi (1.9.25)
globalid (0.4.1)
activesupport (>= 4.2.0)
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
htmlentities (4.3.4)
httparty (0.16.2)
multi_xml (>= 0.5.2)
i18n (1.1.0)
concurrent-ruby (~> 1.0)
io-like (0.3.0)
jbuilder (2.7.0)
activesupport (>= 4.2.0)
multi_json (>= 1.2)
jwt (2.1.0)
kaminari (1.1.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.1.1)
kaminari-activerecord (= 1.1.1)
kaminari-core (= 1.1.1)
kaminari-actionview (1.1.1)
actionview
kaminari-core (= 1.1.1)
kaminari-activerecord (1.1.1)
activerecord
kaminari-core (= 1.1.1)
kaminari-core (1.1.1)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
loofah (2.2.2)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.0)
mini_mime (>= 0.1.1)
marcel (0.3.2)
mimemagic (~> 0.3.2)
method_source (0.9.0)
mimemagic (0.3.2)
mini_mime (1.0.0)
mini_portile2 (2.3.0)
minitest (5.11.3)
msgpack (1.2.4)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
mysql2 (0.5.2)
nio4r (2.3.1)
nokogiri (1.8.4)
mini_portile2 (~> 2.3.0)
oauth2 (1.4.1)
faraday (>= 0.8, < 0.16.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
pdfkit (0.8.4.1)
public_suffix (3.0.2)
puma (3.12.0)
rack (2.0.5)
rack-cors (1.0.2)
rack-protection (2.0.5)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.1)
actioncable (= 5.2.1)
actionmailer (= 5.2.1)
actionpack (= 5.2.1)
actionview (= 5.2.1)
activejob (= 5.2.1)
activemodel (= 5.2.1)
activerecord (= 5.2.1)
activestorage (= 5.2.1)
activesupport (= 5.2.1)
bundler (>= 1.3.0)
railties (= 5.2.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2)
rails-i18n (5.1.3)
i18n (>= 0.7, < 2)
railties (>= 5.0, < 6)
railties (5.2.1)
actionpack (= 5.2.1)
activesupport (= 5.2.1)
method_source
rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0)
rake (12.3.1)
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rchardet (1.8.0)
redis (4.1.0)
redis-actionpack (5.0.2)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 2)
redis-activesupport (5.0.7)
activesupport (>= 3, < 6)
redis-store (>= 1.3, < 2)
redis-rack (2.0.5)
rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.6.0)
redis (>= 2.2, < 5)
roo (2.8.2)
nokogiri (~> 1)
rubyzip (>= 1.2.1, < 2.0.0)
roo-xls (1.2.0)
nokogiri
roo (>= 2.0.0, < 3)
spreadsheet (> 0.9.0)
rqrcode (0.10.1)
chunky_png (~> 1.0)
rqrcode_png (0.1.5)
chunky_png
rqrcode
rspec-core (3.8.0)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-rails (3.8.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.0)
ruby-ole (1.2.12.2)
ruby_dep (1.5.0)
rubyzip (1.2.1)
sass (3.5.7)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sass-rails (5.0.7)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
selenium-webdriver (3.14.0)
childprocess (~> 0.5)
rubyzip (~> 1.2)
sidekiq (5.2.7)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
simple_xlsx_reader (1.0.4)
nokogiri
rubyzip
spreadsheet (1.2.3)
ruby-ole (>= 1.0)
spring (2.0.2)
activesupport (>= 4.2)
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
spring (>= 1.2, < 3.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.1)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thor (0.20.0)
thread_safe (0.3.6)
tilt (2.0.8)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (4.1.17)
execjs (>= 0.3.0, < 3)
unicode-display_width (1.4.0)
web-console (3.6.2)
actionview (>= 5.0)
activemodel (>= 5.0)
bindex (>= 0.4.0)
railties (>= 5.0)
websocket-driver (0.7.0)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
wkhtmltopdf-binary (0.12.4)
xpath (3.1.0)
nokogiri (~> 1.8)
PLATFORMS
ruby
DEPENDENCIES
active_decorator
acts-as-taggable-on (~> 6.0)
awesome_print
axlsx (~> 3.0.0.pre)
axlsx_rails (~> 0.5.2)
bootsnap (>= 1.1.0)
bulk_insert
byebug
capybara (>= 2.15, < 4.0)
chromedriver-helper
faraday (~> 0.15.4)
gitlab!
grape-entity (~> 0.7.1)
jbuilder (~> 2.5)
kaminari (~> 1.1, >= 1.1.1)
listen (>= 3.0.5, < 3.2)
mysql2 (>= 0.4.4, < 0.6.0)
oauth2
pdfkit
puma (~> 3.11)
rack-cors
rails (~> 5.2.0)
rails-i18n (~> 5.1)
rchardet (~> 1.8)
redis-rails
roo-xls
rqrcode (~> 0.10.1)
rqrcode_png
rspec-rails (~> 3.8)
ruby-ole
rubyzip
sass-rails (~> 5.0)
selenium-webdriver
sidekiq
simple_xlsx_reader
spreadsheet
spring
spring-watcher-listen (~> 2.0.0)
tzinfo-data
uglifier (>= 1.3.0)
web-console (>= 3.3.0)
wkhtmltopdf-binary
RUBY VERSION
ruby 2.3.7p456
BUNDLED WITH
1.17.3

@ -0,0 +1,64 @@
# README
This README would normally document whatever steps are necessary to get the
application up and running.
Things you may want to cover:
* Ruby version
* System dependencies
* Configuration
* Database creation
* Database initialization
* How to run the test suite
* Services (job queues, cache servers, search engines, etc.)
* Deployment instructions
* ...
#### Jbuilder介绍
Jbuilder https://github.com/rails/jbuilder
#### Rails5 介绍
rails guide https://ruby-china.github.io/rails-guides/v5.0/
#### API设计文档
doc for api https://www.showdoc.cc/web/#/127895880302646?page_id=729221359592009
user:Hjqreturn PW:12345678
#### 测试版访问地址https://testeduplus2.educoder.net
#### 实训平台繁忙
仓库异常繁忙等级81
#### 新版域名跳转规则
新版域名要求总结testeduplus2.educoder.net/(主要提供实训、实训课堂等业务)
目前有两个域名testbdweb.educoder.net(老版:主要提供课堂、项目、个人主页、后台等服务)
要求:
1、两服务域名都应该启动提供服务
2、如果请求链接包含以下的形式则域名跳至testeduplus2.educoder.net
testeduplus2.educoder.net/shixuns
testeduplus2.educoder.net/shixuns/*
testeduplus2.educoder.net/paths
testeduplus2.educoder.net/paths/*
testeduplus2.educoder.net/myshixuns/
testeduplus2.educoder.net/tasks/*
testeduplus2.educoder.net/games/*
如果不满足上述需求的域名全部跳转至testbdweb.educoder.net
比如门户首页如果访问testeduplus2.educoder.net 应为没包含上述链接。则调制testbdweb.educoder.net
在比如testeduplus2.educoder.net /users/Hjqreturn没包含上述规则则跳转到testbdweb.educoder.net/users/Hjqreturn
# 需要重构user_extensions 相关sql语句的地方标记 REDO:Extention
# 文件上传ActiveStorage
# 新能bootsnap

@ -0,0 +1,6 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative 'config/application'
Rails.application.load_tasks

@ -0,0 +1,3 @@
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

@ -0,0 +1,16 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require_tree .

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,13 @@
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
//
//= require action_cable
//= require_self
//= require_tree ./channels
(function() {
this.App || (this.App = {});
App.cable = ActionCable.createConsumer();
}).call(this);

@ -0,0 +1,4 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,2 @@
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

@ -0,0 +1,16 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/

@ -0,0 +1,3 @@
// Place all the styles related to the boards controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the Challenges controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the course_groups controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the course_modules controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the course_second_categories controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the courses controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the Discusses controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the edu_settings controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the games controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the graduation_tasks controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the graduation_topics controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the graduation_works controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the Home controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the homework_commons controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the memos controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the myshixuns controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the QuestionBank controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the repositories controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,84 @@
body {
background-color: #fff;
color: #333;
margin: 33px;
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
line-height: 18px;
}
p, ol, ul, td {
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
line-height: 18px;
}
pre {
background-color: #eee;
padding: 10px;
font-size: 11px;
}
a {
color: #000;
&:visited {
color: #666;
}
&:hover {
color: #fff;
background-color: #000;
}
}
th {
padding-bottom: 5px;
}
td {
padding: 0 5px 7px;
}
div {
&.field, &.actions {
margin-bottom: 10px;
}
}
#notice {
color: green;
}
.field_with_errors {
padding: 2px;
background-color: red;
display: table;
}
#error_explanation {
width: 450px;
border: 2px solid red;
padding: 7px 7px 0;
margin-bottom: 20px;
background-color: #f0f0f0;
h2 {
text-align: left;
font-weight: bold;
padding: 5px 5px 5px 15px;
font-size: 12px;
margin: -7px -7px 0;
background-color: #c00;
color: #fff;
}
ul li {
font-size: 12px;
list-style: square;
}
}
label {
display: block;
}

@ -0,0 +1,3 @@
// Place all the styles related to the schools controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the stages controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the StudentWorks controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the Subjects controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the tem_tests controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,3 @@
// Place all the styles related to the Users controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -0,0 +1,4 @@
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end

@ -0,0 +1,4 @@
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end

@ -0,0 +1,11 @@
class ReactConstraint
def matches?(request)
# stuff
#
!File.exists?(File.join(Rails.root, "public", request.fullpath.split('?').first))
end
end

@ -0,0 +1,197 @@
class AccountsController < ApplicationController
include ErrorCommon
#skip_before_action :check_account, :only => [:logout]
def index
render json: session
end
# 用户注册
# 注意:用户注册需要兼顾本地版,本地版是不需要验证码及激活码以及使用授权的,注册完成即可使用
# params[:login] 邮箱或者手机号
# params[:code] 验证码
# code_type 1注册手机验证码 8邮箱注册验证码
def register
begin
# 查询验证码是否正确;type只可能是1或者8
type = phone_mail_type(params[:login].strip)
code = params[:code].strip
if type == 1
uid_logger("start register by phone: type is #{type}")
pre = 'p'
email = nil
phone = params[:login]
verifi_code = VerificationCode.where(phone: phone, code: code, code_type: 1).last
else
uid_logger("start register by email: type is #{type}")
pre = 'm'
email = params[:login]
phone = nil
verifi_code = VerificationCode.where(email: email, code: code, code_type: 8).last
end
uid_logger("start register: verifi_code is #{verifi_code}, code is #{code}, time is #{Time.now.to_i - verifi_code.created_at.to_i}")
check_code = (verifi_code.try(:code) == code.strip && (Time.now.to_i - verifi_code.created_at.to_i) <= 10*60)
unless check_code
tip_exception("验证码无效")
end
code = generate_identifier User, 8
login = pre + code
@user = User.new(admin: false, login: login, mail: email, phone: phone)
@user.password = params[:password]
# 现在因为是验证码,所以在注册的时候就可以激活
@user.activate
# 必须要用save操作密码的保存是在users中
if @user.save!
# 注册完成手机号或邮箱想可以奖励500金币
RewardGradeService.call(
@user,
container_id: @user.id,
container_type: pre == 'p' ? 'Phone' : 'Mail',
score: 500
)
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(-1, e.message)
end
end
# 用户登录
def login
@user = User.try_to_login(params[:login], params[:password])
@user.update_column(:last_login_on, Time.now)
successful_authentication(@user)
session[:user_id] = @user.id
end
# 忘记密码
def reset_password
begin
code = params[:code]
login_type = phone_mail_type(params[:login].strip)
# 获取验证码
if login_type == 1
phone = params[:login]
verifi_code = VerificationCode.where(phone: phone, code: code, code_type: 2).last
user = User.find_by_phone(phone)
else
email = params[:login]
verifi_code = VerificationCode.where(email: email, code: code, code_type: 3).last
user = User.find_by_mail(email) #这里有问题应该是为email,而不是mail 6.13-hs
end
check_code = (verifi_code.try(:code) == code.strip && (Time.now.to_i - verifi_code.created_at.to_i) <= 10*60)
unless check_code
tip_exception("验证码无效")
end
user.password, user.password_confirmation = params[:new_password], params[:new_password_confirmation]
if user.save!
sucess_status
end
# rescue Exception => e
# uid_logger_error(e.message)
# tip_exception("密码重置失败,请稍后再试")
end
end
def successful_authentication(user)
uid_logger("Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}")
# Valid user
self.logged_user = user
# generate a key and set cookie if autologin
set_autologin_cookie(user)
UserAction.create(:action_id => user.try(:id), :action_type => "Login", :user_id => user.try(:id))
end
def set_autologin_cookie(user)
token = Token.get_or_create_permanent_login_token(user, "autologin")
cookie_options = {
:value => token.value,
:expires => 1.month.from_now,
:path => '/',
:secure => false,
:httponly => true
}
if edu_setting('cookie_domain').present?
cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain'))
end
cookies[autologin_cookie_name] = cookie_options
logger.info("cookies is #{cookies}")
end
def logout
UserAction.create(action_id: User.current.id, action_type: "Logout", user_id: User.current.id)
session[:user_id] = nil
logout_user
render :json => {status: 1, message: "退出成功!"}
end
# 检验邮箱是否已被注册及邮箱或者手机号是否合法
# 参数type为事件类型 1注册2忘记密码
def valid_email_and_phone
check_mail_and_phone_valid(params[:login], params[:type])
end
# 发送验证码
# params[:login] 手机号或者邮箱号
# params[:type]为事件通知类型 1用户注册注册 2忘记密码 3: 绑定手机 4: 绑定邮箱 # 如果有新的继续后面加
# 发送验证码send_type 1注册手机验证码 2找回密码手机验证码 3找回密码邮箱验证码 4绑定手机 5绑定邮箱
# 6手机验证码登录 7邮箱验证码登录 8邮箱注册验证码
def get_verification_code
code = %W(0 1 2 3 4 5 6 7 8 9)
value = params[:login]
type = params[:type].strip.to_i
login_type = phone_mail_type(value)
send_type = verify_type(login_type, type)
verification_code = code.sample(6).join
# 记录验证码
check_verification_code(verification_code, send_type, value)
sucess_status
end
# 1 手机类型0 邮箱类型
# 注意新版的login是自动名生成的
def phone_mail_type value
value =~ /^1\d{10}$/ ? 1 : 0
end
private
def autologin_cookie_name
edu_setting('autologin_cookie_name') || 'autologin'
end
def logout_user
if User.current.logged?
if autologin = cookies.delete(autologin_cookie_name)
User.current.delete_autologin_token(autologin)
end
User.current.delete_session_token(session[:tk])
self.logged_user = nil
end
session[:user_id] = nil
end
# type 事件类型 1用户注册 2忘记密码 3: 绑定手机 4: 绑定邮箱 # 如果有新的继续后面加
# login_type 1手机类型 2邮箱类型
def verify_type login_type, type
case type
when 1
login_type == 1 ? 1 : 8
when 2
login_type == 1 ? 2 : 3
when 3
login_type == 1 ? 4 : tip_exception('请填写正确的手机号')
when 4
login_type == 1 ? tip_exception('请填写正确的邮箱') : 5
end
end
end

@ -0,0 +1,597 @@
require 'oauth2'
class ApplicationController < ActionController::Base
include CodeExample
include RenderExpand
include RenderHelper
include ControllerRescueHandler
protect_from_forgery prepend: true, unless: -> { request.format.json? }
before_action :user_setup
#before_action :check_account
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)
helper_method :current_user
# 全局配置参数
# 返回name对应的value
def edu_setting name
EduSetting.find_by_name(name).try(:value)
end
def user_course_identity
@user_course_identity = current_user.course_identity(@course)
if @user_course_identity > Course::STUDENT && @course.is_public == 0
tip_exception(403, "您没有权限进入")
end
uid_logger("###############user_course_identity:#{@user_course_identity}")
end
# 判断用户的邮箱或者手机是否可用
# params[:type] 1: 注册2忘记密码
def check_mail_and_phone_valid login, type
unless login =~ /^[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/ || login =~ /^1\d{10}$/
tip_exception("请输入正确的手机号或邮箱")
end
# 考虑到安全参数问题多一次查询去掉Union
user = User.where(phone: login).first || User.where(mail: login).first
if type.to_i == 1 && !user.nil?
tip_exception("该手机号码或邮箱已被注册")
elsif type.to_i == 2 && user.nil?
tip_exception("该手机号码或邮箱未注册")
end
sucess_status
end
# 发送及记录激活码
# 发送验证码type 1注册手机验证码 2找回密码手机验证码 3找回密码邮箱验证码 4绑定手机 5绑定邮箱
# 6手机验证码登录 7邮箱验证码登录 8邮箱注册验证码
def check_verification_code(code, send_type, value)
case send_type
when 1, 2, 4
# 手机类型的发送
sigle_para = {phone: value}
status = Educoder::Sms.send(mobile: value, code: code)
tip_exception(code_msg(status)) if status != 0
when 8, 3, 5
# 邮箱类型的发送
sigle_para = {email: value}
begin
UserMailer.register_email(value, code).deliver_now
# Mailer.run.email_register(code, value)
rescue Exception => e
uid_logger_error(e.message)
tip_exception("邮件发送失败,请稍后重试")
end
end
ver_params = {code_type: send_type, code: code}.merge(sigle_para)
VerificationCode.create!(ver_params)
end
def code_msg status
case status
when 0
"验证码已经发送到您的手机,请注意查收"
when 8
"同一手机号30秒内重复提交相同的内容"
when 9
"同一手机号5分钟内重复提交相同的内容超过3次"
when 22
"1小时内同一手机号发送次数超过限制"
when 33
"验证码发送次数超过频率"
when 43
"一天内同一手机号发送次数超过限制"
end
end
def find_course
return normal_status(2, '缺少course_id参数') if params[:course_id].blank?
@course = Course.find(params[:course_id])
rescue Exception => e
tip_exception(e.message)
end
def course_manager
return normal_status(403, '只有课堂管理员才有权限') if @user_course_identity > Course::CREATOR
end
def find_board
return normal_status(2, "缺少board_id参数") if params[:board_id].blank?
@board = Board.find(params[:board_id])
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
end
def validate_type(object_type)
normal_status(2, "参数") if params.has_key?(:sort_type) && !SORT_TYPE.include?(params[:sort_type].strip)
end
def set_pagination
@page = params[:page] || 1
@page_size = params[:page_size] || 15
end
# 课堂教师权限
def teacher_allowed
logger.info("#####identity: #{current_user.course_identity(@course)}")
unless current_user.course_identity(@course) < Course::STUDENT
normal_status(403, "")
end
end
def require_admin
normal_status(403, "") unless User.current.admin?
end
# 前端会捕捉401,弹登录弹框
# 未授权的捕捉407弹试用申请弹框
def require_login
#6.13 -hs
if User.current.logged?
if !current_user.profile_completed?
info_url = "#{edu_setting('old_edu_host')}/account/user_info"
tip_exception(402, info_url)
# render :json => { status: 402, url: info_url }
elsif current_user.certification != 1
day_cer = UserDayCertification.where(user_id: current_user.id).last
tip_exception(407, "系统未授权") unless (Time.now.to_i - day_cer.try(:created_at).to_i) < 86400
end
else
tip_exception(401, "..")
end
end
# 异常提醒
def tip_exception(status = -1, message)
raise Educoder::TipException.new(status, message)
end
def missing_template
tip_exception(404, "...")
end
# 弹框提醒
def tip_show_exception(status = -2, message)
raise Educoder::TipException.new(status, message)
end
def normal_status(status = 0, message)
case status
when 403
message = "您没有权限进行该操作"
when 404
message = "您访问的页面不存在或已被删除"
end
render :json => { status: status, message: message }
end
# 系统全局认证
#
def check_auth
old_edu_host = edu_setting('old_edu_host')
ue = current_user.user_extension
if current_user.lastname.blank? || ue.school_id.blank? || ue.identity.blank? || current_user.mail.blank?
info_url = old_edu_host + '/account/user_info'
render :json => { status: 402, url: info_url }
elsif current_user.certification != 1
day_cer = UserDayCertification.where(user_id: current_user.id).last
unless (Time.now.to_i - day_cer.try(:created_at).to_i) < 86400
account_url = old_edu_host + "/my/account"
render :json => { status: 402, url: account_url }
end
end
end
# 身份资料的认证:
# 如果试用过期则弹框提示认证,先跳入个人资料页面完善资料,资料完成后,弹框提醒用户试用申请
def check_account
# # todo user_extension
# if User.current.logged?
# ue = current_user.user_extension
# if current_user.lastname.blank? || ue.school_id.blank? || ue.identity.blank? || current_user.mail.blank?
# info_url = "#{edu_setting('old_edu_host')}/account/user_info"
# render :json => { status: 402, url: info_url }
# end
# end
end
# 版本库目录空间
def repo_namespace(user_login, shixun_identifier)
"#{user_login}/#{shixun_identifier}.git"
end
# 版本库文件内容,带转码
def git_fle_content(repo_path, path)
begin
content = GitService.file_content(repo_path: repo_path, path: path)["content"]
logger.info("@@@@@@@@@@@@@@@@@@#{content}")
decode_content = nil
if content.present?
content = Base64.decode64(content)
cd = CharDet.detect(content)
logger.info "encoding: #{cd['encoding']} confidence: #{cd['confidence']}"
decode_content =
if cd["encoding"] == 'GB18030' && cd['confidence'] > 0.8
content.encode('UTF-8', 'GBK', {:invalid => :replace, :undef => :replace, :replace => ' '})
else
content.force_encoding('UTF-8')
end
end
decode_content
rescue Exception => e
uid_logger_error(e.message)
raise Educoder::TipException.new("文档内容获取异常")
end
end
# 更新文件代码
# content 文件内容message提交描述
def update_file_content(content, repo_path, path, mail, username, message)
GitService.update_file(repo_path: repo_path, file_path: path, message: message,
content: content, author_name: username, author_email: mail)
end
# 版本库Fork功能
def project_fork(container, original_rep_path, username)
raise Educoder::TipException.new("fork源路径为空,fork失败!") if original_rep_path.blank?
# 将要生成的仓库名字
new_repo_name = "#{username}/#{container.try(:identifier)}#{ Time.now.strftime("%Y%m%d%H%M%S")}"
uid_logger("start fork container: repo_name is #{new_repo_name}")
GitService.fork_repository(repo_path: original_rep_path, fork_repository_path: (new_repo_name + ".git"))
container.update_attributes!(:repo_name => new_repo_name)
end
def start_user_session(user)
session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i
end
def user_setup
# Find the current user
User.current = find_current_user
uid_logger("user_step: " + (User.current.logged? ? "#{User.current.try(:login)} (id=#{User.current.try(:id)})" : "anonymous"))
if !User.current.logged? && Rails.env.development?
User.current = User.find 12
end
if params[:debug] == 'teacher' #todo 为了测试,记得讲debug删除
User.current = User.find 49610
elsif params[:debug] == 'student'
User.current = User.find 8686
elsif params[:debug] == 'admin'
User.current = User.find 1
end
end
# Sets the logged in user
def logged_user=(user)
reset_session
if user && user.is_a?(User)
User.current = user
start_user_session(user)
else
User.current = User.anonymous
end
end
# Returns the current user or nil if no user is logged in
# and starts a session if needed
def find_current_user
if session[:user_id]
# existing session
(User.active.find(session[:user_id]) rescue nil)
elsif autologin_user = try_to_autologin
autologin_user
elsif params[:format] == 'atom' && params[:key] && request.get? && accept_rss_auth?
# RSS key authentication does not start a session
User.find_by_rss_key(params[:key])
end
end
def autologin_cookie_name
edu_setting('autologin_cookie_name').presence || 'autologin'
end
def try_to_autologin
if cookies[autologin_cookie_name]
# auto-login feature starts a new session
user = User.try_to_autologin(cookies[autologin_cookie_name])
if user
start_user_session(user)
end
user
end
end
def api_request?
%w(xml json).include? params[:format]
end
def current_user
User.current
end
## 默认输出json
def render_json
respond_to do |format|
format.json
end
end
# 以用户id开始的日志定义
def uid_logger(message)
Rails.logger.info("##:#{current_user.try(:id)} --#{message}")
end
# 以用户id开始的日志定义
def uid_logger_error(message)
Rails.logger.error("##:#{current_user.try(:id)} --#{message}")
end
## 输出错误信息
def error_status(message = nil)
@status = -1
@message = message
end
# 实训等对应的仓库地址
def repo_ip_url(repo_path)
"#{edu_setting('git_address_ip')}/#{repo_path}"
end
def repo_url(repo_path)
"#{edu_setting('git_address_domain')}/#{repo_path}"
end
# 通关后,把最后一次成功的代码存到数据库
# type 0 创始内容, 1 最新内容
def game_passed_code(path, myshixun, game_id)
file_content = git_fle_content myshixun.repo_path, path
unless file_content.present?
raise("获取文件代码异常")
end
game_code = GameCode.where(:game_id => game_id, :path => path).first
if game_code.nil?
GameCode.create!(:game_id => game_id, :new_code => file_content, :path => path)
else
game_code.update_attributes!(:new_code => file_content)
end
end
# Post请求
def uri_post(uri, params)
begin
uid_logger("--uri_exec: params is #{params}, url is #{uri}")
uri = URI.parse(URI.encode(uri.strip))
res = Net::HTTP.post_form(uri, params).body
logger.info("--uri_exec: .....res is #{res}")
JSON.parse(res)
rescue Exception => e
uid_logger("--uri_exec: exception #{e.message}")
raise Educoder::TipException.new("实训平台繁忙繁忙等级84")
end
end
# 处理返回非0就报错的请求
def interface_post(uri, params, status, message)
begin
uid_logger("--uri_exec: params is #{params}, url is #{uri}")
uri = URI.parse(URI.encode(uri.strip))
res = Net::HTTP.post_form(uri, params).body
logger.info("--uri_exec: .....res is #{res}")
res = JSON.parse(res)
if (res && res['code'] != 0)
tip_exception(status, message)
else
res
end
rescue Exception => e
uid_logger("--uri_exec: exception #{e.message}")
raise Educoder::TipException.new("实训平台繁忙繁忙等级84")
end
end
#实训题的关卡url初始化
def challenge_path(path)
cha_path = path.present? ? path.split("") : []
cha_path.reject(&:blank?)[0].try(:strip)
end
# 适用与已经用url_safe编码后回调字符串形式
def tran_base64_decode64(str)
s_size = str.size % 4
if s_size != 0
str += "=" * (4 - s_size)
end
if str.blank?
str
else
Base64.decode64(str.tr("-_", "+/")).force_encoding("utf-8")
end
end
def sucess_status(message = 'success')
render :json => { status: 1, message: message }
end
# 随机生成字符
def generate_identifier(container, num)
code = DCODES.sample(num).join
if container == User
while container.exists?(login: code) do
code = DCODES.sample(num).join
end
else
while container.exists?(identifier: code) do
code = DCODES.sample(num).join
end
end
code
end
# 实训主类别列表,自带描述
def shixun_main_type
list = []
mirrors = MirrorRepository.select([:id, :type_name]).published_main_mirror
mirrors.try(:each) do |mirror|
list << {id: mirror.id, type_name: mirror.type_name, description: mirror.try(:description)}
end
list
end
# 小类别列表
def shixun_small_type
list = []
mirrors = MirrorRepository.select([:id, :type_name]).published_small_mirror
mirrors.try(:each) do |mirror|
list << {id: mirror.id, type_name: mirror.type_name}
end
list
end
def container_limit(mirror_repositories)
container = []
mirror_repositories.each do |mr|
if mr.name.present?
container << {:image => mr.name, :cpuLimit => mr.cpu_limit, :memoryLimit => "#{mr.memory_limit}M", :type => mr.try(:main_type) == "1" ? "main" : "sub"}
end
end
container.to_json
end
# 毕设任务列表的赛选
def course_work(task, **option)
logger.info("#############{option}")
course = task.course
work_list = task.graduation_works.includes(user: [:user_extension])
# 教师评阅搜索 0: 未评, 1 已评
if option[:teacher_comment]
graduation_work_ids = GraduationWorkScore.where(graduation_work_id: work_list.map(&:id)).pluck(:graduation_work_id)
if option[:teacher_comment].zero?
work_list = work_list.where.not(id: graduation_work_ids)
elsif option[:teacher_comment] == 1
work_list = work_list.where(id: graduation_work_ids).where.not(work_status: 0)
end
end
# 作品状态 0 未提交, 1 按时提交, 2 延迟提交
if option[:task_status]
work_list = work_list.where(work_status: option[:task_status])
end
# 分班情况
if option[:course_group]
group_user_ids = course.course_members.where(course_group_id: option[:course_group]).pluck(:user_id)
# 有分组只可能是老师身份查看列表
work_list = work_list.where(user_id: group_user_ids)
end
# 只看我的交叉评阅
if option[:cross_comment]
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 如果修改 请调整
if option[:search]
work_list = work_list.joins(user: :user_extension).where("concat(lastname, firstname) like ?
or student_id like ?", "%#{option[:search]}%", "%#{option[:search]}%")
end
# 排序
rorder = option[:order] || "updated_at"
b_order = option[: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_list
end
def strip_html(text, len=0, endss="...")
ss = ""
if !text.nil? && text.length>0
ss=text.gsub(/<\/?.*?>/, '').strip
ss = ss.gsub(/&nbsp;*/, '')
ss = ss.gsub(/\r\n/,'') #新增
ss = ss.gsub(/\n/,'') #新增
if len > 0 && ss.length > len
ss = ss[0, len] + endss
elsif len > 0 && ss.length <= len
ss = ss
#ss = truncate(ss, :length => len)
end
end
ss
end
# Returns a string that can be used as filename value in Content-Disposition header
def filename_for_content_disposition(name)
request.env['HTTP_USER_AGENT'] =~ %r{MSIE|Trident|Edge} ? ERB::Util.url_encode(name) : name
end
def format_time(time)
time.blank? ? '' : time.strftime("%Y-%m-%d %H:%M")
end
# 获取Oauth Client
def get_client(site)
client_id = Rails.configuration.educoder['client_id']
client_secret = Rails.configuration.educoder['client_secret']
OAuth2::Client.new(client_id, client_secret, site: site)
end
def paginate(relation)
limit = params[:limit].to_i.zero? ? 20 : params[:limit].to_i
page = params[:page].to_i.zero? ? 1 : params[:page].to_i
offset = (page - 1) * limit
if relation.is_a?(Array)
relation[offset, limit]
else
relation.limit(limit).offset(offset)
end
end
def strf_time(time)
time.blank? ? '' : time.strftime("%Y-%m-%d %H:%M:%S")
end
def logger_error(error)
Rails.logger.error(error.message)
error.backtrace.each { |msg| Rails.logger.error(msg) }
end
private
def object_not_found
uid_logger("Missing template or cant't find record, responding with 404")
render json: {message: "您访问的页面不存在或已被删除", status: 404}
false
end
def tip_show(exception)
uid_logger("Tip show status is #{exception.status}, message is #{exception.message}")
render json: exception.tip_json
end
def render_parameter_missing
render json: { status: -1, message: '参数缺失' }
end
end

@ -0,0 +1,162 @@
#coding=utf-8
#
# 文件上传
class AttachmentsController < ApplicationController
before_action :require_login
before_action :find_file, only: %i[show destroy]
include ErrorCommon
include ApplicationHelper
def show
# 1. 优先跳到cdn
# 2. 如果没有cdnsend_file
if @file.cloud_url.present?
update_downloads(@file)
redirect_to @file.cloud_url and return
end
send_file absolute_path(local_path(@file)), type: @file.content_type
update_downloads(@file)
end
def create
# 1. 本地存储
# 2. 上传到云
upload_file = params["file"] || params["#{params[:file_param_name]}"] # 这里的file_param_name是为了方便其他插件名称
uid_logger("#########################file_params####{params["#{params[:file_param_name]}"]}")
if upload_file
folder = edu_setting('attachment_folder')
raise "存储目录未定义" unless folder.present?
month_folder = current_month_folder
save_path = File.join(folder, month_folder)
ext = file_ext(upload_file.original_filename)
local_path, digest = file_save_to_local(save_path, upload_file.tempfile, ext)
content_type = upload_file.content_type.presence || 'application/octet-stream'
remote_path = file_save_to_ucloud(local_path[folder.size, local_path.size], local_path, content_type)
logger.info "local_path: #{local_path}"
logger.info "remote_path: #{remote_path}"
disk_filename = local_path[save_path.size + 1, local_path.size]
#存数据库
#
@attachment = Attachment.where(disk_filename: disk_filename,
author_id: current_user.id,
cloud_url: remote_path).first
unless @attachment.present?
@attachment = Attachment.new
@attachment.filename = upload_file.original_filename
@attachment.disk_filename = local_path[save_path.size + 1, local_path.size]
@attachment.filesize = upload_file.tempfile.size
@attachment.content_type = content_type
@attachment.digest = digest
@attachment.author_id = current_user.id
@attachment.disk_directory = month_folder
@attachment.cloud_url = remote_path
@attachment.save!
else
logger.info "文件已存在id = #{@attachment.id}, filename = #{@attachment.filename}"
end
render_json
else
raise "未上传文件"
end
end
def destroy
begin
@file_path = absolute_path(local_path(@file))
#return normal_status(403, "") unless @file.author == current_user
@file.destroy!
delete_file(@file_path)
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
raise ActiveRecord::Rollback
end
end
private
def find_file
@file =
if params[:type] == 'history'
AttachmentHistory.find params[:id]
else
Attachment.find params[:id]
end
end
def delete_file(file_path)
File.delete(file_path) if File.exist?(file_path)
end
def current_month_folder
date = Time.now
"#{date.year}/#{date.day.to_s.rjust(2, '0')}"
end
def file_ext(file_name)
ext = ''
exts = file_name.split(".")
if exts.size > 1
ext = ".#{exts.last}"
end
ext
end
def file_save_to_local(save_path, temp_file, ext)
unless Dir.exists?(save_path)
FileUtils.mkdir_p(save_path) ##不成功这里会抛异常
end
digest = md5_file(temp_file)
digest = "#{digest}_#{Time.now.to_i}"
local_file_path = File.join(save_path, digest) + ext
save_temp_file(temp_file, local_file_path)
[local_file_path, digest]
end
def save_temp_file(temp_file, save_file_path)
File.open(save_file_path, 'wb') do |f|
temp_file.rewind
while (buffer = temp_file.read(8192))
f.write(buffer)
end
end
end
def md5_file(temp_file)
md5 = Digest::MD5.new
temp_file.rewind
while (buffer = temp_file.read(8192))
md5.update(buffer)
end
md5.hexdigest
end
def file_save_to_ucloud(path, file, content_type)
ufile = Educoder::Ufile.new(
ucloud_public_key: edu_setting('public_key'),
ucloud_private_key: edu_setting('private_key'),
ucloud_public_read: true,
ucloud_public_bucket: edu_setting('public_bucket'),
ucloud_public_bucket_host: edu_setting('public_bucket_host'),
ucloud_public_cdn_host: edu_setting('public_cdn_host'),
)
File.open(file) do |f|
ufile.put(path, f, 'Content-Type' => content_type)
end
edu_setting('public_cdn_host') + "/" + path
end
end

@ -0,0 +1,86 @@
class BoardsController < ApplicationController
before_action :require_login
before_action :find_course, only: [:create]
before_action :set_board, except: [:create]
before_action :teacher_allowed
def index
@boards = @course.boards.includes(messages: [:last_reply, :author]);
end
def show
end
def new
end
def create
ActiveRecord::Base.transaction do
begin
board = @course.course_board
new_board = Board.new(board_params)
new_board.course_id = @course.id
new_board.project_id = -1
new_board.parent_id = board.try(:id)
new_board.position = board.children.count + 1
new_board.save!
normal_status(0, "添加成功")
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
raise ActiveRecord::Rollback
end
end
end
# 子目录的拖动
def move_category
tip_exception("移动失败") if params[:position].blank?
unless params[:position].to_i == @board.position
course_board = @course.course_board
if params[:position].to_i < @board.position
course_board.children.where("position < #{@board.position} and position >= ?", params[:position]).update_all("position = position + 1")
else
course_board.children.where("position > #{@board.position} and position <= ?", params[:position]).update_all("position = position - 1")
end
@board.update_attributes(position: params[:position])
normal_status(0, "移动成功")
else
normal_status(-1, "位置没有变化")
end
end
def destroy
ActiveRecord::Base.transaction do
begin
@course_board = @course.course_board
@course_board.children.where("position > #{@board.position}").update_all("position = position - 1")
@board.messages.update_all(board_id: @course_board.id)
@board.destroy
rescue Exception => e
uid_logger_error(e.message)
tip_exception("子目录删除失败")
raise ActiveRecord::Rollback
end
end
end
def update
@board.update_attributes(board_params)
normal_status(0, "更新成功")
end
private
def set_board
@board = Board.find_by!(id: params[:id])
@course = @board.course
end
def board_params
tip_exception("子目录名称不能为空") if params[:name].blank?
tip_exception("已存在同名讨论区") if @course.boards.exists?(name: params[:name].strip)
params.require(:board).permit(:name)
end
end

@ -0,0 +1,305 @@
class ChallengesController < ApplicationController
before_action :require_login
before_action :find_shixun, only: [:new, :create, :index]
skip_before_action :verify_authenticity_token, only: [:create, :update, :create_choose_question, :crud_answer]
before_action :find_challenge, only: [:edit, :show, :update, :create_choose_question, :index_down, :index_up,
:edit_choose_question, :show_choose_question, :destroy_challenge_choose,
:update_choose_question, :destroy, :crud_answer, :answer]
# before_action :allowed, except: [:index, :show, :edit_choose_question, :edit]
include ShixunsHelper
include ChallengesHelper
# 新建实践题
def new
@position = @shixun.challenges.count + 1
@st = params[:st].to_i
@task_pass_default = PlatformSample.find_by(samples_type: "taskPass").try(:contents)
end
# params
# challenge:{"subject": "标题", "task_pass": "过关任务",
# "diffculty": "关卡难度", "score": "关卡分数", "st": "关卡类型"} 关卡相关信息
# challenge_tag: 关卡标签
#
def create
ActiveRecord::Base.transaction do
begin
@challenge = Challenge.new(challenge_params)
@challenge.position = @shixun.challenges.count + 1
@challenge.shixun_id = @shixun.id
@challenge.user_id = current_user.id
@challenge.modify_time = Time.now
@challenge.save!
# 实训是否需要重置, 非实践任务创建第一个阶段调用, 避免不包含实践任务的实训进行模拟实战报错 todo: 新建实训需要重置TPI
# shixun_modify_status_without_publish(@shixun, 1) if @challenge.position != 1
tags = params[:challenge_tag]
if tags.present?
tags.each do |tag|
# TODO 创建tag的时候为什么一直报challenge choose必须存在??
ChallengeTag.create!(name: tag, challenge_id: @challenge.id)
end
end
rescue Exception => e
uid_logger_error("create challenge failed #{e}")
end
end
end
# 创建选择题
def create_choose_question
ActiveRecord::Base.transaction do
begin
@challenge_choose = ChallengeChoose.new(chooce_params)
@challenge_choose.position = @challenge.challenge_chooses.count + 1
@challenge_choose.category = @challenge_choose.standard_answer.length == 1 ? 1 : 2
@challenge_choose.challenge_id = @challenge.id
if @challenge_choose.save!
# 创建选项
params[:question][:cnt].each_with_index do |test, index|
answer = params[:choice][:answer][index]
ChallengeQuestion.create(:option_name => test,
:challenge_choose_id => @challenge_choose.id,
:position => index, :right_key => answer)
end
# 创建单选多选的技能标签
if params[:challenge_tag].present?
params[:challenge_tag].each do |tag|
ChallengeTag.create(:name => tag, :challenge_choose_id => @challenge_choose.id, :challenge_id => @challenge.id)
end
end
end
rescue Exception => e
raise ActiveRecord::Rollback
end
end
end
# 选择题详情页面
def show_choose_question
@challenge_choose = ChallengeChoose.find params[:choose_id]
end
# 选择题更新页面
def update_choose_question
@challenge_choose = ChallengeChoose.find(params[:choose_id])
ActiveRecord::Base.transaction do
if params[:standard_answer] != @challenge_choose.standard_answer
@challenge.update_column(:modify_time, Time.now)
end
@challenge_choose.update_attributes(chooce_params)
# 单选多选题的更新
category = @challenge_choose.standard_answer.length > 1 ? 2 : 1
@challenge_choose.update_column(:category, category)
begin
@challenge_choose.challenge_questions.delete_all
params[:question][:cnt].each_with_index do |test, index|
answer = params[:choice][:answer][index]
ChallengeQuestion.create(:option_name => test, :challenge_choose_id => @challenge_choose.id, :position => index, :right_key => answer)
end
@challenge_choose.challenge_tags.delete_all unless @challenge_choose.challenge_tags.blank?
if params[:challenge_tag].present?
params[:challenge_tag].each do |tag|
ChallengeTag.create(:name => tag, :challenge_choose_id => @challenge_choose.id, :challenge_id => @challenge.id)
end
end
@challenge_choose = ChallengeChoose.find params[:choose_id]
rescue Exception => e
raise ActiveRecord::Rollback
end
end
end
# 选择题的编辑
def edit_choose_question
@challenge_choose = ChallengeChoose.find params[:choose_id]
end
def destroy_challenge_choose
ActiveRecord::Base.transaction do
@challenge_choose = ChallengeChoose.where(:id => params[:choose_id]).first
pos = @challenge_choose.position
@challenge.challenge_chooses.where("position > ?", pos).update_all("position = position - 1")
@challenge_choose.destroy
@status = 1
# 发起重置请求 TODO: 重置实训需要后续做
# shixun_modify_status_without_publish(@shixun, 1)
end
end
# 编辑模式
# tab 0,nil 过关任务, 1 评测设置, 2 参考答案
def edit
@tab = params[:tab].to_i
@power = current_user.manager_of_shixun?(@shixun) && @shixun.status == 0
challenge_num = Challenge.where(:shixun_id => @shixun).count
@position = @challenge.position
@chooses = @challenge.challenge_chooses
if @position < challenge_num
@next_challenge = Challenge.where(:shixun_id => @shixun, :position => @position + 1).first
end
@prev_challenge = Challenge.where(:shixun_id => @shixun, :position => @position - 1).first if @position - 1 > 0
end
def index
uid_logger("identifier: #{params}")
# 通过调试发现 这里includes(:games)性能会慢10倍
@challenges = @shixun.challenges.fields_for_list
@editable = current_user.manager_of_shixun?(@shixun) && @shixun.status == 0
@user = current_user
end
def show
@tab = params[:tab].nil? ? 1 : params[:tab].to_i
challenge_num = Challenge.where(shixun_id: @shixun).count
@power = current_user.manager_of_shixun?(@shixun) && @shixun.status == 0
@position = @challenge.position
if @position < challenge_num
@next_challenge = Challenge.where(:shixun_id => @shixun, :position => @position + 1).first
end
@prev_challenge = Challenge.where(:shixun_id => @shixun, :position => @position - 1).first if @position - 1 > 0
end
# tab 0:过关任务的更新; 1:评测设置的更新; 2:表示参考答案的更新;
def update
ActiveRecord::Base.transaction do
tab = params[:tab].to_i
@challenge.update_attributes(challenge_params)
if tab == 0 && @challenge.st == 0
@challenge.challenge_tags.delete_all
if params[:challenge_tag].present?
params[:challenge_tag].each do |input|
ChallengeTag.create!(:name => input, :challenge_id => @challenge.id)
end
end
elsif tab == 1
path = @challenge.path
exec_path = @challenge.exec_path
test_set = @challenge.test_sets
sets_output = test_set.map(&:output)
sets_input = test_set.map(&:input)
sets_open = test_set.map(&:is_public)
set_score = test_set.map(&:score)
params_hidden = params[:test_set].map{|set| set[:hidden].to_i == 0}
params_output = params[:test_set].map{|set| set[:output] }
params_input = params[:test_set].map{|set| set[:input] }
params_score = params[:test_set].map{|set| set[:score]}
# 测试集变化则需要更新(输入、 输出、 是否隐藏)
if sets_output != params_output || sets_open != params_hidden || sets_input != params_input || set_score != params_score
test_set.delete_all unless test_set.blank?
params[:test_set].each_with_index do |set, index|
TestSet.create(:challenge_id => @challenge.id,
:input => "#{set[:input]}",
:output => "#{set[:output]}",
:is_public => params_hidden[index],
:score => set[:score],
:position => (index + 1))
end
@challenge.update_column(:modify_time, Time.now)
# 测试集的
@shixun.myshixuns.update_all(:system_tip => 0)
end
# 关卡评测执行文件如果被修改,需要修改脚本内容
script = modify_shixun_script @shixun, @shixun.evaluate_script
@shixun.update_column(:evaluate_script, script)
# TODO:
# if path != params[:challenge][:path]
# shixun_modify_status_without_publish(@shixun, 1)
# end
#Attachment.attach_files(@challenge, params[:attachments])
end
end
end
# 参考答案的'增,删,改'
# POST: /shixuns/:id/challenges/:id/crud_answer
# {'challenge_answer': [
# {'name': 'name', contents: 'contents', score: 10},
# {...},
# {...}, ...]
#}
def crud_answer
raise '参考答案不能为空' if params[:challenge_answer].empty?
raise '占比之和必须为100%' if params[:challenge_answer].map{|a| a[:score]}.sum != 100
@challenge.challenge_answers.destroy_all if @challenge.challenge_answers
params[:challenge_answer].each_with_index do |answer, index|
ChallengeAnswer.create(name: answer[:name], contents: answer[:contents],
level: index+1, score: answer[:score], challenge_id: @challenge.id)
end
end
# 查看参考答案接口
def answer
@answers = @challenge.challenge_answers
end
def index_down
next_challenge = @challenge.next_challenge
position = @challenge.position
@challenge.update_attribute(:position, (position + 1))
next_challenge.update_attribute(:position, next_challenge.position - 1)
# 关卡位置被修改,需要修改脚本
script = modify_shixun_script @shixun, @shixun.evaluate_script
@shixun.update_column(:evaluate_script, script)
end
def index_up
position = @challenge.position
last_challenge = @challenge.last_challenge
@challenge.update_attribute(:position, (position - 1))
last_challenge.update_attribute(:position, last_challenge.position + 1)
# 关卡位置被修改,需要修改脚本
script = modify_shixun_script @shixun, @shixun.evaluate_script
@shixun.update_column(:evaluate_script, script)
end
def destroy
next_challenges = @shixun.challenges.where("position > #{@challenge.position}")
next_challenges.update_all("position = position - 1")
# Todo: 实训修改后,关卡需要重置
# shixun_modify_status_without_publish(@shixun, 1)
@challenge.destroy
# 关卡位置被删除,需要修改脚本
script = modify_shixun_script @shixun, @shixun.evaluate_script
@shixun.update_column(:evaluate_script, script)
end
private
def find_shixun
@shixun = Shixun.find_by_identifier(params[:shixun_identifier])
end
# 通用接口
def find_challenge
@challenge = Challenge.find params[:id]
@shixun = Shixun.find_by!(identifier: params[:shixun_identifier])
end
def challenge_params
params.require(:challenge).permit(:subject, :task_pass, :difficulty, :score, :st, :modify_time, :test_set_average,
:path, :exec_path, :show_type, :original_picture_path, :test_set_score,
:expect_picture_path, :picture_path, :web_route, :answer)
end
def chooce_params
params.require(:challenge_choose).permit(:subject, :answer,
:standard_answer, :score, :difficult)
end
def allowed
# 实训为发布前,除实训的管理者外,其他人都不人都不允许访问
if !current_user.manager_of_shixun?(@shixun) && (@shixun.status < 1 || @shixun.hidden == 1)
raise Educoder::TipException.new(403, "..")
end
end
end

@ -0,0 +1,58 @@
class CommonsController < ApplicationController
OBJECT_TYPE = %W[message journals_for_message]
before_action :require_login
before_action :validate_object_type
before_action :find_object
before_action :validate_power
def delete
begin
@object.destroy!
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
raise ActiveRecord::Rollback
end
end
def hidden
action(true)
end
def unhidden
action(false)
end
private
def find_object
begin
@object = 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_object_type
return normal_status(2, "缺少object_id参数") if params[:object_id].blank?
return normal_status(2, "缺少object_type参数") if params[:object_type].blank?
return normal_status(2, "object_type参数格式错误") unless OBJECT_TYPE.include? params[:object_type].strip
end
def validate_power
tip_exception(403, "无操作权限") unless current_user.admin?
end
def action(flag)
begin
@object.has_attribute?(:is_hidden) ? @object.update_attributes(:is_hidden => flag )
: @object.update_attributes(:hidden => flag )
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
raise ActiveRecord::Rollback
end
end
end

@ -0,0 +1,137 @@
module CodeExample
extend ActiveSupport::Concern
#老师C语言的标准代码
def c_stantard_code_teacher
"// 老师您好这是一个C语言的样例程序
//
//
// 123
// 347
// ... ...
//
// argv
//
#include <stdio.h> //引用必须头文件
int main(int argc, char** argv) {
int a = atoi(argv[1]); //
int b = atoi(argv[2]); //
printf(\"%d\",a+b); //输出a+b
return 0;
}".html_safe
end
#老师C++语言的标准代码
def c_stantard_code_teacher_
"// 老师您好这是一个C++语言的样例程序
//
//
// 123
// 347
// ... ...
//
// argv
//
#include <iostream> //引用必须头文件
#include <cstdlib>
using namespace std;
int main(int argc, char** argv){
int a = atoi(argv[1]); //
int b = atoi(argv[2]); //
cout<<a+b; //a+b
return 0;
}".html_safe
end
#学生C语言的标准代码
def c_stantard_code_student
"// 同学好这是一个C语言的样例程序
//
//
// 123
// 347
// ... ...
//
// argv
//
#include <stdio.h> //引用必须头文件
int main(int argc, char** argv) {
int a = atoi(argv[1]); //
int b = atoi(argv[2]); //
printf(\"%d\",a+b); //输出a+b
return 0;
}".html_safe
end
#学生C++语言的标准代码
def c_stantard_code_student_
"// 同学好这是一个C++语言的样例程序
//
//
// 123
// 347
// ... ...
//
// argv
//
#include <iostream> //引用必须头文件
#include <cstdlib>
using namespace std;
int main(int argc, char** argv){
int a = atoi(argv[1]); //
int b = atoi(argv[2]); //
cout<<a+b; //a+b
return 0;
}".html_safe
end
def compile_command
"compile(){
# 编译命令
compileCommand=\"COMPILECOMMAND\"
# 取当前关卡的编译文件
challengeProgramName=${challengeProgramNames[$1 - 1]}
# 获取编译结果(此处编译无输出则说明编译通过,否则输出编译错误信息,请按实训实际情况调整)
compileResult=$($compileCommand $challengeProgramName 2>&1 | base64)
if [ -z \"$compileResult\" ]; then
compileResult=$(echo -n \"compile successfully\" | base64)
fi
}
compile $1"
end
def execute_command
"execute(){
#执行命令
executeCommand=\"EXECUTECOMMAND\"
#执行文件名
sourceClassName=${sourceClassNames[$1 - 1]}
challengeStage=$1
output=''
i=0
while [[ i -lt ${#ins[*]} ]]; do
#执行,并拼接执行结果
result=$(echo \"${ins[$i]}\" | base64 -d | $executeCommand $sourceClassName 2>&1 | base64)
#拼接输出结果
output=$output\\\"$result\\\",
let i++
done
output=\"[${output%?}]\"
}
execute $1
"
end
end

@ -0,0 +1,16 @@
module ControllerRescueHandler
extend ActiveSupport::Concern
included do
# rescue_from ActionView::MissingTemplate, with: :object_not_found
# rescue_from ActiveRecord::RecordNotFound, with: :object_not_found
rescue_from Educoder::TipException, with: :tip_show
rescue_from ::ActionView::MissingTemplate, with: :missing_template
rescue_from ActiveRecord::RecordNotFound, with: :object_not_found
rescue_from ActionController::ParameterMissing, with: :render_parameter_missing
# form validation error
rescue_from ActiveModel::ValidationError do |ex|
render_error(ex.model.errors.full_messages.join(','))
end
end
end

@ -0,0 +1,10 @@
module ErrorCommon
extend ActiveSupport::Concern
included do
rescue_from Exception do |e|
logger.error e
render json: {status: -1, message: e.message}
end
end
end

@ -0,0 +1,48 @@
module GitCommon
extend ActiveSupport::Concern
included do
end
# ------------------------
# 版本库目录结构
def repository
logger.info("ssssssseeeeeeee#{params}")
begin
@repo_url = repo_url @repo_path
@trees = GitService.file_tree(repo_path: @repo_path, path: @path)
logger.info("#11@@#@#@#@111#@@@@###{@trees}")
# TPI(学员实训)不需要获取最近的一次提交
if params[:controller] != "myshixuns" && @trees
logger.info("#@@#@#@#@#@@@@###{@trees.try(:count)}")
@latest_commit = [GitService.commits(repo_path: @repo_path).first]
Rails.logger.info("########## #{@latest_commit}")
end
rescue Exception => e
logger.error(e.message)
end
end
def file_content
logger.info("#################{@repo_path}, #{@path}")
@content = git_fle_content @repo_path, @path
end
# 版本库提交记录
# Redo: commit接口需要按倒叙排列
def commits
begin
@commits = GitService.commits(repo_path: @repo_path)
logger.info("git first commit is #{@commits.try(:first)}")
raise Educoder::TipException.new("请先创建版本库") if @commits.nil?
rescue Exception => e
uid_logger_error(e.message)
raise Educoder::TipException.new("提交记录异常")
end
end
end

@ -0,0 +1,20 @@
module RenderExpand
extend ActiveSupport::Concern
included do
ActionController.add_renderer :pdf do |template, options|
file = File.open(Rails.root.join('app/templates', template << '.html.erb'))
html = ERB.new(file.read).result(binding)
kit = PDFKit.new(html)
base_css = %w(app/templates/shared/main.css)
base_css.each { |css| kit.stylesheets << Rails.root.join(css) }
Array.wrap(options.delete(:stylesheets)).each do |path|
kit.stylesheets << Rails.root.join('app/templates', path)
end
send_data kit.to_pdf, filename: options[:filename], type: 'application/pdf'
end
end
end

@ -0,0 +1,19 @@
module RenderHelper
def render_ok(data = {})
render json: { status: 0, message: 'success' }.merge(data)
end
def render_error(message = '')
render json: { status: -1, message: message }
end
def render_not_found(message = I18n.t('error.record_not_found'))
render json: { status: 404, message: message }
# render status: 404, json: { errors: errors }
end
def render_forbidden(message = I18n.t('error.forbidden'))
render json: { status: 403, message: message }
# render status: 403, json: { errors: errors }
end
end

@ -0,0 +1,64 @@
class CourseGroupsController < ApplicationController
before_action :require_login
before_action :set_group, except: [:create]
before_action :find_course, only: [:create]
before_action :teacher_allowed
def create
tip_exception("分班名称不能为空") if params[:name].blank?
if @course.course_groups.where(name: params[:name]).count > 0
normal_status(-1, "已存在同名分班")
else
@course.course_groups.create!(name: params[:name], position: @course.course_groups.count + 1)
normal_status(0, "创建成功")
end
end
def destroy
ActiveRecord::Base.transaction do
begin
@course.course_groups.where("position > #{@group.position}").update_all("position = position - 1")
# 将该分班的学生转到未分班
@group.course_members.update_all(course_group_id: 0)
@group.destroy
rescue Exception => e
uid_logger_error(e.message)
tip_exception("删除分班失败")
end
end
end
# 分班重命名
def rename_group
tip_exception("名称不能为空") if params[:name].blank?
if @course.course_groups.where(name: params[:name]).count > 0
normal_status(-1, "已存在同名分班")
else
@group.update_attributes(name: params[:name].strip)
normal_status(0, "更新成功")
end
end
# 分班的拖动
def move_category
tip_exception("移动失败") if params[:position].blank?
unless params[:position].to_i == @group.position
if params[:position].to_i < @group.position
@course.course_groups.where("position < #{@group.position} and position >= ?", params[:position]).update_all("position = position + 1")
else
@course.course_groups.where("position > #{@group.position} and position <= ?", params[:position]).update_all("position = position - 1")
end
@group.update_attributes(position: params[:position])
normal_status(0, "移动成功")
else
normal_status(-1, "位置没有变化")
end
end
private
def set_group
@group = CourseGroup.find_by!(id: params[:id])
@course = @group.course
end
end

@ -0,0 +1,66 @@
class CourseModulesController < ApplicationController
before_action :require_login
before_action :set_module, except: [:unhidden_modules]
before_action :find_course, only: [:unhidden_modules]
before_action :teacher_allowed
# 模块置顶
def sticky_module
# position为1则不做处理否则该模块的position置为1position小于当前模块的position加1
unless @course_module.position == 1
@course.course_modules.where("position < #{@course_module.position}").update_all("position = position + 1")
@course_module.update_attributes(position: 1)
end
normal_status(0, "置顶成功")
end
# 模块隐藏
def hidden_module
@course_module.update_attributes(hidden: 1)
normal_status(0, "更新成功")
end
# 模块重命名
def rename_module
name = params[:name].strip
tip_exception("名称不能为空") if name.blank?
tip_exception("已存在同名模块") if @course.course_modules.exists?(module_name: name)
@course_module.update_attributes(module_name: name)
case @course_module.module_type
when 'board'
@course.course_board.update_columns(name: name)
end
normal_status(0, "更新成功")
end
# 模块的显示
def unhidden_modules
tip_exception("请选择要显示的模块") if params[:module_ids].blank?
@course.course_modules.where(id: params[:module_ids]).update_all(hidden: 0)
normal_status(0, "更新成功")
end
# 添加二级目录
def add_second_category
tip_exception("子目录名称不能为空") if params[:name].blank?
tip_exception("已存在同名子目录") if @course_module.course_second_categories.exists?(name: params[:name].strip)
ActiveRecord::Base.transaction do
begin
@course_module.course_second_categories.create!(name: params[:name].strip, category_type: @course_module.module_type,
course_id: @course.id, position: @course_module.course_second_categories.count + 1)
normal_status(0, "添加成功")
rescue Exception => e
uid_logger_error(e.message)
tip_exception("添加子目录失败")
end
end
end
private
def set_module
@course_module = CourseModule.find_by!(id: params[:id])
@course = @course_module.course
end
end

@ -0,0 +1,60 @@
class CourseSecondCategoriesController < ApplicationController
before_action :require_login
before_action :set_category
before_action :teacher_allowed
# 目录重命名
def rename_category
tip_exception("毕设子目录不能重命名") if @category.category_type == "graduation"
tip_exception("名称不能为空") if params[:name].blank?
tip_exception("已存在同名子目录") if @course_module.course_second_categories.exists?(name: params[:name].strip)
@category.update_attributes(name: params[:name].strip)
normal_status(0, "更新成功")
end
# 子目录的拖动
def move_category
tip_exception("移动失败") if params[:position].blank?
unless params[:position].to_i == @category.position
if params[:position].to_i < @category.position
@course_module.course_second_categories.where("position < #{@category.position} and position >= ?", params[:position]).update_all("position = position + 1")
else
@course_module.course_second_categories.where("position > #{@category.position} and position <= ?", params[:position]).update_all("position = position - 1")
end
@category.update_attributes(position: params[:position])
normal_status(0, "移动成功")
else
normal_status(-1, "位置没有变化")
end
end
def destroy
tip_exception("毕设子目录不能删除") if @category.category_type == "graduation"
ActiveRecord::Base.transaction do
begin
@course_module.course_second_categories.where("position > #{@category.position}").update_all("position = position - 1")
# 更新相应对象的子目录id
if @course_module.module_type == "shixun_homework"
@category.homework_commons.update_all(course_second_category_id: 0)
@right_url = "/courses/#{@course.id}/shixun_homeworks/#{@course_module.id}"
elsif @course_module.module_type == "attachment"
Attachment.where(course_second_category_id: @category.id).update_all(course_second_category_id: 0)
@right_url = "/courses/#{@course.id}/files/#{@course_module.id}"
end
@category.destroy
rescue Exception => e
uid_logger_error(e.message)
tip_exception("删除子目录失败")
end
end
end
private
def set_category
@category = CourseSecondCategory.find_by!(id: params[:id])
@course_module = @category.course_module
@course = @course_module.try(:course)
end
end

File diff suppressed because it is too large Load Diff

@ -0,0 +1,127 @@
class DiscussesController < ApplicationController
LIMIT = 10
before_action :find_container, only: [:index, :hidden]
before_action :find_discuss, except: [:create, :index, :new_message, :reward_code]
def index
page = params[:page].to_i
offset = page * LIMIT
# 总数,分页使用
@disscuss_count = Discuss.where(:dis_id => @container.id, :dis_type => @container.class.to_s, :root_id => nil).count
@discusses = Discuss.limit(LIMIT).where(:dis_id => @container.id, :dis_type => @container.class.to_s, :root_id => nil).
includes(:user, :praise_tread).offset(offset)
@current_user = current_user
end
def new_message
onclick_time = Myshixun.find(params[:myshixun_id]).try(:onclick_time)
ids = Discuss.where(user_id: User.current.id, dis_id: params[:container_id], dis_type: params[:container_type]).
where("parent_id is null").pluck(:id)
@user_discuss = Discuss.where("user_id !=? and created_at >?", User.current.id, onclick_time).where(parent_id: ids,
dis_id: params[:container_id], dis_type: params[:container_type]).first
end
def create
begin
@discuss = Discuss.create!(:dis_id => params[:container_id], :dis_type => params[:container_type], :content => params[:content].gsub("&nbsp\;", "").strip,
:user_id => current_user.id, :praise_count => 0, :position => params[:position], :challenge_id => params[:challenge_id])
rescue Exception => e
uid_logger_error("create discuss failed : #{e.message}")
raise Educoder::TipException.new("评论异常")
end
end
def reply
begin
@discuss = Discuss.create!(:content => params[:content].gsub("&nbsp\;", "").strip, :user_id => current_user.id,
:root_id => @discuss.root_id || params[:id], :parent_id => params[:id], :praise_count => 0,
:challenge_id => @discuss.challenge_id, :dis_id => @discuss.dis_id,
:dis_type => @discuss.dis_type, :position => @discuss.position)
rescue Exception => e
uid_logger_error("reply discuss failed : #{e.message}")
raise Educoder::TipException.new("回复评论异常")
end
end
# 隐藏评论
def hidden
logger.info("################{current_user.id}")
if current_user.manager_of_shixun?(@container)
logger.info("66666666#{current_user.id}")
@discuss.update_attribute(:hidden, params[:hidden] == "1")
sucess_status
else
Educoder::TipException(403, "..")
end
end
# 点/取消赞,一个用户只能点一次
# 0 取消赞;
def plus
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
@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
unless current_user.admin?
Educoder::TipException(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
# Post为帖子回复类型这个名字取的...
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
@code = score
end
end
def destroy
begin
@discuss.destroy
sucess_status
rescue Exception => e
uid_logger_error("destroy discuss failed: #{e.message}")
Educoder::TipException("帖子删除异常")
end
end
private
def find_container
@container = Shixun.select([:id, :user_id]).find_by_identifier(params[:container_identifier])
end
def find_discuss
@discuss = Discuss.select([:id, :hidden, :reward, :dis_type, :dis_id, :position, :challenge_id, :root_id]).find(params[:id])
end
end

@ -0,0 +1,61 @@
class Ecs::BaseController < ApplicationController
# model validation error
rescue_from ActiveRecord::RecordInvalid do |ex|
render_error(ex.record.errors.full_messages.join(','))
end
# form validation error
rescue_from ActiveModel::ValidationError do |ex|
render_error(ex.model.errors.full_messages.join(','))
end
before_action :require_login
before_action :check_user_permission!
helper_method :current_user, :current_school
private
# --- 每个子类controller可能有不同的实现查看时需要注意 ---
def current_year
@_current_year ||= EcYear.find(params[:ec_year_id])
end
def current_major_school
@_current_major_school ||= current_year.ec_major_school
end
def current_school
@_current_school ||= current_major_school.school
end
def major_or_course_manager?
!current_user.admin? && !current_school.manager?(current_user)
end
def check_user_permission!
return if current_user.admin? || current_school.manage_permission?(current_user)
render_forbidden
end
def check_manager_permission!
return if current_user.admin? || current_school.manager?(current_user)
render_forbidden
end
def check_major_manager_permission!
return if current_user.admin? || current_school.manager?(current_user)
return if current_major_school.manager?(current_user)
render_forbidden
end
def paginate(objs)
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
Kaminari.paginate_array(objs).page(page).per(per_page)
end
end

@ -0,0 +1,20 @@
class Ecs::CourseAchievementMethodsController < Ecs::CourseBaseController
def show
include_associations = { ec_course_achievement_methods: [:ec_course_evaluation, :ec_course_evaluation_subitems] }
@course_targets = current_course.ec_course_targets.includes(include_associations)
end
def create
@course_target = Ecs::CreateCourseAchievementMethodsService.call(current_course_target, create_params)
end
private
def create_params
params.permit(course_achievement_methods: %i[id course_evaluation_id course_evaluation_subitem_ids score percentage])
end
def current_course_target
@_current_course_target ||= current_course.ec_course_targets.find(params[:course_target_id])
end
end

@ -0,0 +1,9 @@
class Ecs::CourseBaseController < Ecs::BaseController
def current_course
@_current_course ||= EcCourse.find(params[:ec_course_id])
end
def current_year
@_current_year ||= current_course.ec_year
end
end

@ -0,0 +1,64 @@
class Ecs::CourseEvaluationsController < Ecs::CourseBaseController
def index
@course_evaluations = current_course.ec_course_evaluations.includes(:ec_course_evaluation_subitems)
end
def create
course_evaluation = current_course.ec_course_evaluations.new
@course_evaluation = Ecs::SaveCourseEvaluationService.call(course_evaluation, create_params)
render 'show'
end
def update
@course_evaluation = Ecs::SaveCourseEvaluationService.call(current_course_evaluation, update_params)
render 'show'
end
def destroy
current_course_evaluation.destroy!
render_ok
end
def slimmer
course_evaluations = current_course.ec_course_evaluations
if params[:course_evaluation_id].present?
course_evaluations = course_evaluations.where(id: params[:course_evaluation_id])
end
@course_evaluations = course_evaluations.includes(:ec_course_evaluation_subitems)
end
def average_score_import_template
@course_evaluation = current_course_evaluation
filename = "#{@course_evaluation.name}平均成绩导入模板_#{Time.current.strftime('%Y%m%d%H%M%S')}.xlsx"
render xlsx: 'average_score_import_template', filename: filename
end
def detail_score_import_template
@course_evaluation = current_course_evaluation
filename = "#{@course_evaluation.name}明细成绩导入模板_#{Time.current.strftime('%Y%m%d%H%M%S')}.xlsx"
render xlsx: 'detail_score_import_template', filename: filename
end
def import_student_achievement
Ecs::ImportCourseStudentAchievementService.call(current_course_evaluation, params[:attachment_id])
render_ok
rescue Ecs::ImportCourseStudentAchievementService => ex
render_error(ex.message)
end
private
def create_params
params.permit(:name, :evaluation_count, :status, course_evaluation_subitems: [:name])
end
def update_params
params.permit(:name, :evaluation_count, :status, course_evaluation_subitems: [:id, :name])
end
def current_course_evaluation
@_current_course_evaluation ||= current_course.ec_course_evaluations.find(params[:id])
end
end

@ -0,0 +1,20 @@
class Ecs::CourseManagersController < Ecs::CourseBaseController
skip_before_action :check_user_permission!, only: [:create, :destroy]
before_action :check_major_manager_permission!, only: [:create, :destroy]
def create
@user = Ecs::CreateCourseManagerService.call(current_course, params[:user_id])
rescue Ecs::CreateCourseManagerService::Error => ex
render_error(ex.message)
end
def destroy
# params[:id] 为 user_id
manager = current_course.ec_course_users.find_by(user_id: params[:id])
return render_error('不存在该课程管理员!') if manager.blank?
manager.destroy!
render_ok
end
end

@ -0,0 +1,30 @@
class Ecs::CourseTargetsController < Ecs::CourseBaseController
def index
@course_targets = current_course.ec_course_targets.includes(:ec_graduation_subitems)
respond_to do |format|
format.json
format.xlsx do
filename = "#{current_year.year}届_#{current_course.name}_课程目标_#{Time.current.strftime('%Y%m%d%H%M%S')}.xlsx"
render xlsx: 'index', filename: filename
end
end
end
def create
Ecs::CreateCourseTargetsService.call(current_course, create_params)
@course_targets = current_course.ec_course_targets.includes(:ec_graduation_subitems)
render 'index'
end
def with_achievement_methods
@course_targets = current_course.ec_course_targets.includes(:ec_graduation_subitems, :ec_course_achievement_methods)
end
private
def create_params
params.permit(course_targets: %i[id content standard_grade weight graduation_subitem_id])
end
end

@ -0,0 +1,75 @@
class Ecs::EcCoursesController < Ecs::BaseController
before_action :check_major_manager_permission!, only: [:create, :import, :destroy]
def index
ec_courses = current_year.ec_courses
@count = ec_courses.count
@ec_courses = paginate ec_courses.includes(:course_managers)
course_ids = @ec_courses.map(&:id)
# 课程目标数
@target_count_map = EcCourseTarget.where(ec_course_id: course_ids).group(:ec_course_id).count
# 考核方式数
@evaluation_count_map = EcCourseEvaluation.where(ec_course_id: course_ids).group(:ec_course_id).count
# 评价方法数
@achievement_method_count_map = EcCourseAchievementMethod.where(ec_course_id: course_ids).group(:ec_course_id).count
# 毕业目标数
@graduation_subitem_count_map = EcGraduationSubitemCourseTarget.joins(:ec_course_target)
.where(ec_course_targets: { ec_course_id: course_ids })
.group('ec_course_targets.ec_course_id')
.count('distinct ec_graduation_subitem_id')
# 已达成毕业目标数
@completed_subitem_count_map = EcCourseSupport.joins(:ec_graduation_requirement_calculation)
.where(ec_course_id: course_ids)
.where(ec_graduation_requirement_calculations: { status: 1 })
.group('ec_course_supports.ec_course_id')
.count('distinct ec_graduation_subitem_id')
end
def search
ec_courses = current_year.ec_courses
search = params[:search].to_s.strip
if search
ec_courses = ec_courses.where('name LIKE ?', "%#{search}%")
end
@count = ec_courses.count
@ec_courses = paginate ec_courses
end
def create
create_params = params.permit(:name)
@ec_course = Ecs::CreateCourseService.call(current_year, create_params)
rescue Ecs::CreateCourseService::Error => ex
render_error(ex.message)
end
def destroy
current_course.destroy!
render_ok
end
def import
success_count = Ecs::ImportCourseService.call(current_year, params[:attachment_id])
render_ok(success_count: success_count)
rescue Ecs::ImportCourseService::Error => ex
render_error(ex.message)
end
def link_course
link_params = params.permit(:course_id)
Ecs::LinkCourseService.call(current_course, link_params)
render_ok
rescue Ecs::LinkCourseService::Error => ex
render_error(ex.message)
end
private
def current_course
@_current_course ||= current_year.ec_courses.find(params[:id])
end
end

@ -0,0 +1,38 @@
class Ecs::EcGraduationRequirementsController < Ecs::BaseController
skip_before_action :check_user_permission!, only: [:create, :update]
before_action :check_major_manager_permission!, only: [:create, :update]
def index
@graduation_requirements = current_year.ec_graduation_requirements.includes(:ec_graduation_subitems)
respond_to do |format|
format.json
format.xlsx do
filename = "#{current_year.year}届毕业要求及指导点_#{Time.current.strftime('%Y%m%d%H%M%S')}.xlsx"
render xlsx: 'index', filename: filename
end
end
end
def create
graduation_requirement = current_year.graduation_requirements.new
@graduation_requirement = Ecs::SaveGraduationRequirementeService.call(graduation_requirement, create_params)
render 'show'
end
def update
graduation_requirement = current_year.graduation_requirements.find(params[:id])
@graduation_requirement = Ecs::SaveGraduationRequirementeService.call(graduation_requirement, update_params)
render 'show'
end
private
def create_params
params.permit(:position, :content, graduation_subitems: [:content])
end
def update_params
params.permit(:position, :content, graduation_subitems: [:id, :content])
end
end

@ -0,0 +1,56 @@
class Ecs::EcMajorSchoolsController < Ecs::BaseController
def index
major_schools = current_school.ec_major_schools.not_template
# 专业/课程管理员,则仅显示他们所在的专业
if major_or_course_manager?
ec_major_school_ids = current_user.ec_major_school_users.pluck(:ec_major_school_id)
ec_course_major_subquery = current_user.ec_course_users.select(:ec_year_id)
ec_year_school_ids = EcYear.where(id: ec_course_major_subquery).pluck(:ec_major_school_id)
major_schools = major_schools.where(id: (ec_major_school_ids + ec_year_school_ids).uniq)
end
if params[:search].present?
major_ids_subquery = EcMajor.search_name_or_code(params[:search]).select(:id)
major_schools = major_schools.where(ec_major_id: major_ids_subquery)
end
@count = major_schools.count #检索后的数量,小于或等于全部数量
@major_schools = paginate(major_schools.includes(:users, :ec_major))
@template_major_school = current_school.ec_major_schools.is_template.first #示例专业
end
def create
ActiveRecord::Base.transaction do
Array(params[:major_ids].presence).each do |id|
EcMajorSchool.create!(ec_major_id: id, school_id: current_school.id)
end
end
render_ok
end
def destroy
return render_forbidden if major_or_course_manager?
major_school = current_school.ec_major_schools.find(params[:id])
if major_school.template_major?
render_error('示例专业不能被删除')
return
end
major_school.destroy!
render_ok
end
private
def current_school
@_current_school ||= School.find(params[:school_id])
end
end

@ -0,0 +1,19 @@
class Ecs::EcMajorsController < Ecs::BaseController
def index
school_major_subquery = current_school.ec_major_schools.select(:ec_major_id) #学校已选择的专业
ec_majors = EcMajor.where.not(id: school_major_subquery)
if params[:search].present?
ec_majors = ec_majors.search_name_or_code(params[:search])
end
@count = ec_majors.count
@ec_majors = paginate(ec_majors)
end
private
def current_school
@_current_school ||= School.find(params[:school_id])
end
end

@ -0,0 +1,26 @@
class Ecs::EcTrainingObjectivesController < Ecs::BaseController
before_action :check_major_manager_permission!, only: [:create]
def show
@training_objective = current_year.ec_training_objective
respond_to do |format|
format.json
format.xlsx do
filename = "#{@training_objective.ec_year.year}届培养目标_#{Time.current.strftime('%Y%m%d%H%M%S')}.xlsx"
render xlsx: 'show', filename: filename
end
end
end
def create
@training_objective = Ecs::CreateTrainingObjectiveService.call(current_year, create_params)
render 'show'
end
private
def create_params
params.permit(:content, training_subitems: [:id, :content])
end
end

@ -0,0 +1,57 @@
class Ecs::EcYearsController < Ecs::BaseController
before_action :check_major_manager_permission!, only: [:create, :destroy]
def index
ec_years = current_major_school.ec_years
search = params[:search].to_s.strip
if search.present?
ec_years = ec_years.where('year LIKE ?', "%#{search}%")
end
@count = ec_years.count
@ec_years = paginate ec_years
return if @ec_years.blank?
# 防止N+1手动查询数量
year_ids = @ec_years.pluck(:id)
@student_count_map = EcYearStudent.where(ec_year_id: year_ids).group(:ec_year_id).count
@training_subitem_count_map = EcTrainingSubitem.joins(:ec_training_objective)
.where(ec_training_objectives: { ec_year_id: year_ids }).group('ec_year_id').count
@graduation_requirement_count_map = EcGraduationRequirement.where(ec_year_id: year_ids).group(:ec_year_id).count
@course_count_map = EcCourse.where(ec_year_id: year_ids).group(:ec_year_id).count
@course_target_count_map = EcCourseTarget.joins(:ec_course).where(ec_courses: { ec_year_id: year_ids })
.group('ec_year_id').count
@graduation_subitem_count_map = EcGraduationSubitem.joins(:ec_graduation_requirement)
.where(ec_graduation_requirements: { ec_year_id: year_ids }).group('ec_year_id').count
end
def create
if current_major_school.ec_years.exists?(year: params[:year].to_i)
render_error('届别已存在')
return
end
@ec_year = CopyEcYearService.call(current_major_school, params[:year].to_i)
end
def destroy
current_year.destroy!
render_ok
end
private
def current_year
@_current_year ||= current_major_school.ec_years.find(params[:id])
end
def current_major_school
@_ec_major_school ||= EcMajorSchool.find(params[:ec_major_school_id])
end
def current_school
@_current_school ||= current_major_school.school
end
end

@ -0,0 +1,17 @@
class Ecs::EvaluationsController < Ecs::CourseBaseController
def show
service = Ecs::QueryCourseEvaluationService.new(current_course)
render_ok(
course_targets: service.course_targets,
course_achievement: service.course_achievement,
graduation_subitem_evaluations: service.graduation_subitem_evaluations,
score_levels_map: service.score_levels_map
)
end
def create
Ecs::CalculateCourseEvaluationService.call(current_course)
render_ok
end
end

@ -0,0 +1,28 @@
class Ecs::GraduationCourseSupportsController < Ecs::BaseController
before_action :check_major_manager_permission!, only: [:create]
def show
@graduation_subitems = current_year.ec_graduation_subitems
.includes(:ec_graduation_requirement, ec_course_supports: :ec_course)
@course_count = current_year.ec_courses.count
respond_to do |format|
format.json
format.xlsx do
filename = "#{current_year.year}届课程体系对毕业要求的支撑_#{Time.current.strftime('%Y%m%d%H%M%S')}.xlsx"
render xlsx: 'show', filename: filename
end
end
end
def create
graduation_subitem = current_year.ec_graduation_subitems.find(params[:graduation_subitem_id])
@graduation_subitem = Ecs::SaveGraduationCourseSupportsService.call(graduation_subitem, create_params)
end
private
def create_params
params.permit(course_supports: %i[id ec_course_id weights top_relation])
end
end

@ -0,0 +1,11 @@
class Ecs::HomesController < Ecs::BaseController
def index
@school_managers = current_school.users
end
private
def current_school
@_current_school ||= School.find(params[:school_id])
end
end

@ -0,0 +1,30 @@
class Ecs::MajorManagersController < Ecs::BaseController
skip_before_action :check_user_permission!
before_action :check_manager_permission!
def create
@user = Ecs::CreateMajorManagerService.call(current_major_school, params[:user_id])
rescue Ecs::CreateMajorManagerService::Error => ex
render_error(ex.message)
end
def destroy
# params[:id] 为 user_id
manager = current_major_school.ec_major_school_users.find_by(user_id: params[:id])
return render_error('不存在该专业管理员!') if manager.blank?
manager.destroy!
render_ok
end
private
def current_major_school
@_ec_major_school ||= EcMajorSchool.find(params[:ec_major_school_id])
end
def current_school
@_current_school ||= current_major_school.school
end
end

@ -0,0 +1,16 @@
class ReachCriteriaController < Ecs::BaseController
before_action :check_major_manager_permission!, only: [:create]
def create
reach_criteria = params[:reach_criteria].to_f
if reach_criteria.zero?
render_error('必须大于0')
return
end
current_year.update!(calculation_value: reach_criteria)
render_ok
end
end

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

Loading…
Cancel
Save