Description:
add model solution
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r854:14d6c4fb29b0 - - 12 files changed: 267 inserted, 180 deleted

@@ -0,0 +1,6
1 + class AddTypeToSubmission < ActiveRecord::Migration[7.0]
2 + def change
3 + add_column :submissions, :tag, :integer, default: 0
4 + add_column :problems, :difficulty, :integer
5 + end
6 + end
@@ -1,137 +1,148
1 require 'ipaddr'
1 require 'ipaddr'
2 require "securerandom"
2 require "securerandom"
3
3
4 class ApplicationController < ActionController::Base
4 class ApplicationController < ActionController::Base
5 protect_from_forgery
5 protect_from_forgery
6
6
7 before_action :current_user
7 before_action :current_user
8 before_action :nav_announcement
8 before_action :nav_announcement
9 before_action :unique_visitor_id
9 before_action :unique_visitor_id
10
10
11 SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode'
11 SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode'
12 MULTIPLE_IP_LOGIN_CONF_KEY = 'right.multiple_ip_login'
12 MULTIPLE_IP_LOGIN_CONF_KEY = 'right.multiple_ip_login'
13 WHITELIST_IGNORE_CONF_KEY = 'right.whitelist_ignore'
13 WHITELIST_IGNORE_CONF_KEY = 'right.whitelist_ignore'
14 WHITELIST_IP_CONF_KEY = 'right.whitelist_ip'
14 WHITELIST_IP_CONF_KEY = 'right.whitelist_ip'
15
15
16 #report and redirect for unauthorized activities
16 #report and redirect for unauthorized activities
17 def unauthorized_redirect(notice = 'You are not authorized to view the page you requested')
17 def unauthorized_redirect(notice = 'You are not authorized to view the page you requested')
18 flash[:notice] = notice
18 flash[:notice] = notice
19 redirect_to login_main_path
19 redirect_to login_main_path
20 end
20 end
21
21
22 # Returns the current logged-in user (if any).
22 # Returns the current logged-in user (if any).
23 def current_user
23 def current_user
24 return nil unless session[:user_id]
24 return nil unless session[:user_id]
25 @current_user ||= User.find(session[:user_id])
25 @current_user ||= User.find(session[:user_id])
26 end
26 end
27
27
28 def nav_announcement
28 def nav_announcement
29 @nav_announcement = Announcement.where(on_nav_bar: true)
29 @nav_announcement = Announcement.where(on_nav_bar: true)
30 end
30 end
31
31
32 def admin_authorization
32 def admin_authorization
33 return false unless check_valid_login
33 return false unless check_valid_login
34 user = User.includes(:roles).find(session[:user_id])
34 user = User.includes(:roles).find(session[:user_id])
35 unless user.admin?
35 unless user.admin?
36 unauthorized_redirect
36 unauthorized_redirect
37 return false
37 return false
38 end
38 end
39 return true
39 return true
40 end
40 end
41
41
42 + #admin always count as every roles
43 + def role_authorization(roles)
44 + return false unless check_valid_login
45 + user = User.find(session[:user_id])
46 + return true if user.admin?
47 + roles.each do |r|
48 + return true if user.has_role?(r)
49 + end
50 + unauthorized_redirect
51 + end
52 +
42 def authorization_by_roles(allowed_roles)
53 def authorization_by_roles(allowed_roles)
43 return false unless check_valid_login
54 return false unless check_valid_login
44 unless @current_user.roles.detect { |role| allowed_roles.member?(role.name) }
55 unless @current_user.roles.detect { |role| allowed_roles.member?(role.name) }
45 unauthorized_redirect
56 unauthorized_redirect
46 return false
57 return false
47 end
58 end
48 end
59 end
49
60
50 def testcase_authorization
61 def testcase_authorization
51 #admin always has privileged
62 #admin always has privileged
52 if @current_user.admin?
63 if @current_user.admin?
53 return true
64 return true
54 end
65 end
55
66
56 unauthorized_redirect unless GraderConfiguration["right.view_testcase"]
67 unauthorized_redirect unless GraderConfiguration["right.view_testcase"]
57 end
68 end
58
69
59 def unique_visitor_id
70 def unique_visitor_id
60 unless cookies.encrypted[:uuid]
71 unless cookies.encrypted[:uuid]
61 value = SecureRandom.uuid
72 value = SecureRandom.uuid
62 cookies.encrypted[:uuid] = { value: value, expires: 20.year }
73 cookies.encrypted[:uuid] = { value: value, expires: 20.year }
63 end
74 end
64 puts "encrypt " + cookies.encrypted[:uuid]
75 puts "encrypt " + cookies.encrypted[:uuid]
65 puts cookies[:uuid]
76 puts cookies[:uuid]
66 end
77 end
67
78
68 protected
79 protected
69
80
70 #redirect to root (and also force logout)
81 #redirect to root (and also force logout)
71 #if the user is not logged_in or the system is in "ADMIN ONLY" mode
82 #if the user is not logged_in or the system is in "ADMIN ONLY" mode
72 def check_valid_login
83 def check_valid_login
73 #check if logged in
84 #check if logged in
74 unless session[:user_id]
85 unless session[:user_id]
75 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
86 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
76 unauthorized_redirect('You need to login but you cannot log in at this time')
87 unauthorized_redirect('You need to login but you cannot log in at this time')
77 else
88 else
78 unauthorized_redirect('You need to login')
89 unauthorized_redirect('You need to login')
79 end
90 end
80 return false
91 return false
81 end
92 end
82
93
83 # check if run in single user mode
94 # check if run in single user mode
84 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
95 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
85 if @current_user==nil || (!@current_user.admin?)
96 if @current_user==nil || (!@current_user.admin?)
86 unauthorized_redirect('You cannot log in at this time')
97 unauthorized_redirect('You cannot log in at this time')
87 return false
98 return false
88 end
99 end
89 end
100 end
90
101
91 # check if the user is enabled
102 # check if the user is enabled
92 unless @current_user.enabled? || @current_user.admin?
103 unless @current_user.enabled? || @current_user.admin?
93 unauthorized_redirect 'Your account is disabled'
104 unauthorized_redirect 'Your account is disabled'
94 return false
105 return false
95 end
106 end
96
107
97 # check if user ip is allowed
108 # check if user ip is allowed
98 unless @current_user.admin? || GraderConfiguration[WHITELIST_IGNORE_CONF_KEY]
109 unless @current_user.admin? || GraderConfiguration[WHITELIST_IGNORE_CONF_KEY]
99 unless is_request_ip_allowed?
110 unless is_request_ip_allowed?
100 unauthorized_redirect 'Your IP is not allowed to login at this time.'
111 unauthorized_redirect 'Your IP is not allowed to login at this time.'
101 return false
112 return false
102 end
113 end
103 end
114 end
104
115
105 if GraderConfiguration.multicontests?
116 if GraderConfiguration.multicontests?
106 return true if @current_user.admin?
117 return true if @current_user.admin?
107 begin
118 begin
108 if @current_user.contest_stat(true).forced_logout
119 if @current_user.contest_stat(true).forced_logout
109 flash[:notice] = 'You have been automatically logged out.'
120 flash[:notice] = 'You have been automatically logged out.'
110 redirect_to :controller => 'main', :action => 'index'
121 redirect_to :controller => 'main', :action => 'index'
111 end
122 end
112 rescue
123 rescue
113 end
124 end
114 end
125 end
115 return true
126 return true
116 end
127 end
117
128
118 #redirect to root (and also force logout)
129 #redirect to root (and also force logout)
119 #if the user use different ip from the previous connection
130 #if the user use different ip from the previous connection
120 # only applicable when MULTIPLE_IP_LOGIN options is false only
131 # only applicable when MULTIPLE_IP_LOGIN options is false only
121 def authenticate_by_ip_address
132 def authenticate_by_ip_address
122 #this assume that we have already authenticate normally
133 #this assume that we have already authenticate normally
123 unless GraderConfiguration[MULTIPLE_IP_LOGIN_CONF_KEY]
134 unless GraderConfiguration[MULTIPLE_IP_LOGIN_CONF_KEY]
124 user = User.find(session[:user_id])
135 user = User.find(session[:user_id])
125 if (!user.admin? && user.last_ip && user.last_ip != request.remote_ip)
136 if (!user.admin? && user.last_ip && user.last_ip != request.remote_ip)
126 flash[:notice] = "You cannot use the system from #{request.remote_ip}. Your last ip is #{user.last_ip}"
137 flash[:notice] = "You cannot use the system from #{request.remote_ip}. Your last ip is #{user.last_ip}"
127 redirect_to :controller => 'main', :action => 'login'
138 redirect_to :controller => 'main', :action => 'login'
128 return false
139 return false
129 end
140 end
130 unless user.last_ip
141 unless user.last_ip
131 user.last_ip = request.remote_ip
142 user.last_ip = request.remote_ip
132 user.save
143 user.save
133 end
144 end
134 end
145 end
135 return true
146 return true
136 end
147 end
137
148
@@ -1,170 +1,170
1 class MainController < ApplicationController
1 class MainController < ApplicationController
2
2
3 before_action :check_valid_login, :except => [:login]
3 before_action :check_valid_login, :except => [:login]
4 before_action :check_viewability, :except => [:index, :login]
4 before_action :check_viewability, :except => [:index, :login]
5
5
6 append_before_action :confirm_and_update_start_time,
6 append_before_action :confirm_and_update_start_time,
7 :except => [:index,
7 :except => [:index,
8 :login,
8 :login,
9 :confirm_contest_start]
9 :confirm_contest_start]
10
10
11 # to prevent log in box to be shown when user logged out of the
11 # to prevent log in box to be shown when user logged out of the
12 # system only in some tab
12 # system only in some tab
13 prepend_before_action :reject_announcement_refresh_when_logged_out,
13 prepend_before_action :reject_announcement_refresh_when_logged_out,
14 :only => [:announcements]
14 :only => [:announcements]
15
15
16 before_action :authenticate_by_ip_address, :only => [:list]
16 before_action :authenticate_by_ip_address, :only => [:list]
17
17
18 #reset login, clear session
18 #reset login, clear session
19 #front page
19 #front page
20 def login
20 def login
21 saved_notice = flash[:notice]
21 saved_notice = flash[:notice]
22 reset_session
22 reset_session
23 flash.now[:notice] = saved_notice
23 flash.now[:notice] = saved_notice
24 @remote_ip = request.remote_ip
24 @remote_ip = request.remote_ip
25
25
26 # EXPERIMENT:
26 # EXPERIMENT:
27 # Hide login if in single user mode and the url does not
27 # Hide login if in single user mode and the url does not
28 # explicitly specify /login
28 # explicitly specify /login
29 #
29 #
30 # logger.info "PATH: #{request.path}"
30 # logger.info "PATH: #{request.path}"
31 # if GraderConfiguration['system.single_user_mode'] and
31 # if GraderConfiguration['system.single_user_mode'] and
32 # request.path!='/main/login'
32 # request.path!='/main/login'
33 # @hidelogin = true
33 # @hidelogin = true
34 # end
34 # end
35
35
36 @announcements = Announcement.frontpage
36 @announcements = Announcement.frontpage
37 render :action => 'login', :layout => 'empty'
37 render :action => 'login', :layout => 'empty'
38 end
38 end
39
39
40 def logout
40 def logout
41 reset_session
41 reset_session
42 redirect_to root_path
42 redirect_to root_path
43 end
43 end
44
44
45 def list
45 def list
46 prepare_list_information
46 prepare_list_information
47 end
47 end
48
48
49 def help
49 def help
50 @user = User.find(session[:user_id])
50 @user = User.find(session[:user_id])
51 end
51 end
52
52
53 def submit
53 def submit
54 user = User.find(session[:user_id])
54 user = User.find(session[:user_id])
55
55
56 @submission = Submission.new
56 @submission = Submission.new
57 @submission.problem_id = params[:submission][:problem_id]
57 @submission.problem_id = params[:submission][:problem_id]
58 @submission.user = user
58 @submission.user = user
59 @submission.language_id = 0
59 @submission.language_id = 0
60 if (params['file']) and (params['file']!='')
60 if (params['file']) and (params['file']!='')
61 @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
61 @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
62 @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
62 @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
63 @submission.source_filename = params['file'].original_filename
63 @submission.source_filename = params['file'].original_filename
64 end
64 end
65
65
66 if (params[:editor_text])
66 if (params[:editor_text])
67 language = Language.find_by_id(params[:language_id])
67 language = Language.find_by_id(params[:language_id])
68 @submission.source = params[:editor_text]
68 @submission.source = params[:editor_text]
69 @submission.source_filename = "live_edit.#{language.ext}"
69 @submission.source_filename = "live_edit.#{language.ext}"
70 @submission.language = language
70 @submission.language = language
71 end
71 end
72
72
73 @submission.submitted_at = Time.new.gmtime
73 @submission.submitted_at = Time.new.gmtime
74 - @submission.ip_address = request.remote_ip
74 + @submission.ip_address = cookies.encrypted[:uuid]
75
75
76 if @current_user.admin? == false && GraderConfiguration.time_limit_mode? && @current_user.contest_finished?
76 if @current_user.admin? == false && GraderConfiguration.time_limit_mode? && @current_user.contest_finished?
77 @submission.errors.add(:base,"The contest is over.")
77 @submission.errors.add(:base,"The contest is over.")
78 prepare_list_information
78 prepare_list_information
79 render :action => 'list' and return
79 render :action => 'list' and return
80 end
80 end
81
81
82 if @submission.valid?(@current_user)
82 if @submission.valid?(@current_user)
83 if @submission.save == false
83 if @submission.save == false
84 flash[:notice] = 'Error saving your submission'
84 flash[:notice] = 'Error saving your submission'
85 elsif Task.create(:submission_id => @submission.id,
85 elsif Task.create(:submission_id => @submission.id,
86 :status => Task::STATUS_INQUEUE) == false
86 :status => Task::STATUS_INQUEUE) == false
87 flash[:notice] = 'Error adding your submission to task queue'
87 flash[:notice] = 'Error adding your submission to task queue'
88 end
88 end
89 else
89 else
90 prepare_list_information
90 prepare_list_information
91 render :action => 'list' and return
91 render :action => 'list' and return
92 end
92 end
93 redirect_to edit_submission_path(@submission)
93 redirect_to edit_submission_path(@submission)
94 end
94 end
95
95
96 def source
96 def source
97 submission = Submission.find(params[:id])
97 submission = Submission.find(params[:id])
98 if ((submission.user_id == session[:user_id]) and
98 if ((submission.user_id == session[:user_id]) and
99 (submission.problem != nil) and
99 (submission.problem != nil) and
100 (submission.problem.available))
100 (submission.problem.available))
101 send_data(submission.source,
101 send_data(submission.source,
102 {:filename => submission.download_filename,
102 {:filename => submission.download_filename,
103 :type => 'text/plain'})
103 :type => 'text/plain'})
104 else
104 else
105 flash[:notice] = 'Error viewing source'
105 flash[:notice] = 'Error viewing source'
106 redirect_to :action => 'list'
106 redirect_to :action => 'list'
107 end
107 end
108 end
108 end
109
109
110 def compiler_msg
110 def compiler_msg
111 @submission = Submission.find(params[:id])
111 @submission = Submission.find(params[:id])
112 if @submission.user_id == session[:user_id]
112 if @submission.user_id == session[:user_id]
113 render :action => 'compiler_msg', :layout => 'empty'
113 render :action => 'compiler_msg', :layout => 'empty'
114 else
114 else
115 flash[:notice] = 'Error viewing source'
115 flash[:notice] = 'Error viewing source'
116 redirect_to :action => 'list'
116 redirect_to :action => 'list'
117 end
117 end
118 end
118 end
119
119
120 def result
120 def result
121 if !GraderConfiguration.show_grading_result
121 if !GraderConfiguration.show_grading_result
122 redirect_to :action => 'list' and return
122 redirect_to :action => 'list' and return
123 end
123 end
124 @user = User.find(session[:user_id])
124 @user = User.find(session[:user_id])
125 @submission = Submission.find(params[:id])
125 @submission = Submission.find(params[:id])
126 if @submission.user!=@user
126 if @submission.user!=@user
127 flash[:notice] = 'You are not allowed to view result of other users.'
127 flash[:notice] = 'You are not allowed to view result of other users.'
128 redirect_to :action => 'list' and return
128 redirect_to :action => 'list' and return
129 end
129 end
130 prepare_grading_result(@submission)
130 prepare_grading_result(@submission)
131 end
131 end
132
132
133 def load_output
133 def load_output
134 if !GraderConfiguration.show_grading_result or params[:num]==nil
134 if !GraderConfiguration.show_grading_result or params[:num]==nil
135 redirect_to :action => 'list' and return
135 redirect_to :action => 'list' and return
136 end
136 end
137 @user = User.find(session[:user_id])
137 @user = User.find(session[:user_id])
138 @submission = Submission.find(params[:id])
138 @submission = Submission.find(params[:id])
139 if @submission.user!=@user
139 if @submission.user!=@user
140 flash[:notice] = 'You are not allowed to view result of other users.'
140 flash[:notice] = 'You are not allowed to view result of other users.'
141 redirect_to :action => 'list' and return
141 redirect_to :action => 'list' and return
142 end
142 end
143 case_num = params[:num].to_i
143 case_num = params[:num].to_i
144 out_filename = output_filename(@user.login,
144 out_filename = output_filename(@user.login,
145 @submission.problem.name,
145 @submission.problem.name,
146 @submission.id,
146 @submission.id,
147 case_num)
147 case_num)
148 if !FileTest.exists?(out_filename)
148 if !FileTest.exists?(out_filename)
149 flash[:notice] = 'Output not found.'
149 flash[:notice] = 'Output not found.'
150 redirect_to :action => 'list' and return
150 redirect_to :action => 'list' and return
151 end
151 end
152
152
153 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
153 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
154 response.headers['Content-Type'] = "application/force-download"
154 response.headers['Content-Type'] = "application/force-download"
155 response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\""
155 response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\""
156 response.headers["X-Sendfile"] = out_filename
156 response.headers["X-Sendfile"] = out_filename
157 response.headers['Content-length'] = File.size(out_filename)
157 response.headers['Content-length'] = File.size(out_filename)
158 render :nothing => true
158 render :nothing => true
159 else
159 else
160 send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain"
160 send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain"
161 end
161 end
162 end
162 end
163
163
164 def error
164 def error
165 @user = User.find(session[:user_id])
165 @user = User.find(session[:user_id])
166 end
166 end
167
167
168 # announcement refreshing and hiding methods
168 # announcement refreshing and hiding methods
169
169
170 def announcements
170 def announcements
@@ -141,272 +141,281
141 record = record.pluck("users.id,users.login,users.full_name,count(logins.created_at),min(logins.created_at),max(logins.created_at)")
141 record = record.pluck("users.id,users.login,users.full_name,count(logins.created_at),min(logins.created_at),max(logins.created_at)")
142 record.each do |user|
142 record.each do |user|
143 x = Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
143 x = Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
144 user[0],@since_time,@until_time)
144 user[0],@since_time,@until_time)
145 .pluck(:ip_address).uniq
145 .pluck(:ip_address).uniq
146 @users << { id: user[0],
146 @users << { id: user[0],
147 login: user[1],
147 login: user[1],
148 full_name: user[2],
148 full_name: user[2],
149 count: user[3],
149 count: user[3],
150 min: user[4],
150 min: user[4],
151 max: user[5],
151 max: user[5],
152 ip: x
152 ip: x
153 }
153 }
154 end
154 end
155 end
155 end
156
156
157 def login_detail_query
157 def login_detail_query
158 @logins = Array.new
158 @logins = Array.new
159
159
160 date_and_time = '%Y-%m-%d %H:%M'
160 date_and_time = '%Y-%m-%d %H:%M'
161 begin
161 begin
162 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
162 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
163 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
163 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
164 rescue
164 rescue
165 @since_time = DateTime.new(1000,1,1)
165 @since_time = DateTime.new(1000,1,1)
166 end
166 end
167 begin
167 begin
168 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
168 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
169 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
169 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
170 rescue
170 rescue
171 @until_time = DateTime.new(3000,1,1)
171 @until_time = DateTime.new(3000,1,1)
172 end
172 end
173
173
174 @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
174 @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
175 case params[:users]
175 case params[:users]
176 when 'enabled'
176 when 'enabled'
177 @logins = @logins.where(users: {enabled: true})
177 @logins = @logins.where(users: {enabled: true})
178 when 'group'
178 when 'group'
179 @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
179 @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
180 end
180 end
181 end
181 end
182
182
183 def submission
183 def submission
184 end
184 end
185
185
186 def submission_query
186 def submission_query
187 @submissions = Submission
187 @submissions = Submission
188 .includes(:problem).includes(:user).includes(:language)
188 .includes(:problem).includes(:user).includes(:language)
189
189
190 case params[:users]
190 case params[:users]
191 when 'enabled'
191 when 'enabled'
192 @submissions = @submissions.where(users: {enabled: true})
192 @submissions = @submissions.where(users: {enabled: true})
193 when 'group'
193 when 'group'
194 @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
194 @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
195 end
195 end
196
196
197 case params[:problems]
197 case params[:problems]
198 when 'enabled'
198 when 'enabled'
199 @submissions = @submissions.where(problems: {available: true})
199 @submissions = @submissions.where(problems: {available: true})
200 when 'selected'
200 when 'selected'
201 @submissions = @submissions.where(problem_id: params[:problem_id])
201 @submissions = @submissions.where(problem_id: params[:problem_id])
202 end
202 end
203
203
204 #set default
204 #set default
205 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
205 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
206
206
207 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
207 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
208 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
208 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
209 date_filter: 'submitted_at',
209 date_filter: 'submitted_at',
210 date_param_since: 'since_datetime',
210 date_param_since: 'since_datetime',
211 date_param_until: 'until_datetime',
211 date_param_until: 'until_datetime',
212 hard_limit: 100_000
212 hard_limit: 100_000
213 )
213 )
214 end
214 end
215
215
216 def login
216 def login
217 end
217 end
218
218
219 def problem_hof
219 def problem_hof
220 # gen problem list
220 # gen problem list
221 @user = User.find(session[:user_id])
221 @user = User.find(session[:user_id])
222 @problems = @user.available_problems
222 @problems = @user.available_problems
223
223
224 # get selected problems or the default
224 # get selected problems or the default
225 if params[:id]
225 if params[:id]
226 begin
226 begin
227 @problem = Problem.available.find(params[:id])
227 @problem = Problem.available.find(params[:id])
228 rescue
228 rescue
229 redirect_to action: :problem_hof
229 redirect_to action: :problem_hof
230 flash[:notice] = 'Error: submissions for that problem are not viewable.'
230 flash[:notice] = 'Error: submissions for that problem are not viewable.'
231 return
231 return
232 end
232 end
233 end
233 end
234
234
235 return unless @problem
235 return unless @problem
236
236
237 + #model submisssion
238 + @model_subs = Submission.where(problem: @problem,tag: Submission.tags[:model])
239 +
240 +
241 + #calculate best submission
237 @by_lang = {} #aggregrate by language
242 @by_lang = {} #aggregrate by language
238
243
239 range =65
244 range =65
240 - @histogram = { data: Array.new(range,0), summary: {} }
245 + #@histogram = { data: Array.new(range,0), summary: {} }
241 @summary = {count: 0, solve: 0, attempt: 0}
246 @summary = {count: 0, solve: 0, attempt: 0}
242 user = Hash.new(0)
247 user = Hash.new(0)
243 Submission.where(problem_id: @problem.id).find_each do |sub|
248 Submission.where(problem_id: @problem.id).find_each do |sub|
244 #histogram
249 #histogram
245 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
250 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
246 - @histogram[:data][d.to_i] += 1 if d < range
251 + #@histogram[:data][d.to_i] += 1 if d < range
247
252
248 next unless sub.points
253 next unless sub.points
249 @summary[:count] += 1
254 @summary[:count] += 1
250 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
255 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
251
256
252 lang = Language.find_by_id(sub.language_id)
257 lang = Language.find_by_id(sub.language_id)
253 next unless lang
258 next unless lang
254 next unless sub.points >= @problem.full_score
259 next unless sub.points >= @problem.full_score
255
260
256 #initialize
261 #initialize
257 unless @by_lang.has_key?(lang.pretty_name)
262 unless @by_lang.has_key?(lang.pretty_name)
258 @by_lang[lang.pretty_name] = {
263 @by_lang[lang.pretty_name] = {
259 runtime: { avail: false, value: 2**30-1 },
264 runtime: { avail: false, value: 2**30-1 },
260 memory: { avail: false, value: 2**30-1 },
265 memory: { avail: false, value: 2**30-1 },
261 length: { avail: false, value: 2**30-1 },
266 length: { avail: false, value: 2**30-1 },
262 first: { avail: false, value: DateTime.new(3000,1,1) }
267 first: { avail: false, value: DateTime.new(3000,1,1) }
263 }
268 }
264 end
269 end
265
270
266 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
271 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
267 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
272 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
268 end
273 end
269
274
270 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
275 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
271 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
276 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
272 end
277 end
273
278
274 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
279 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
275 !sub.user.admin?
280 !sub.user.admin?
276 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
281 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
277 end
282 end
278
283
279 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
284 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
280 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
285 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
281 end
286 end
282 end
287 end
283
288
284 #process user_id
289 #process user_id
285 @by_lang.each do |lang,prop|
290 @by_lang.each do |lang,prop|
286 prop.each do |k,v|
291 prop.each do |k,v|
287 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
292 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
288 end
293 end
289 end
294 end
290
295
291 #sum into best
296 #sum into best
292 if @by_lang and @by_lang.first
297 if @by_lang and @by_lang.first
293 @best = @by_lang.first[1].clone
298 @best = @by_lang.first[1].clone
294 @by_lang.each do |lang,prop|
299 @by_lang.each do |lang,prop|
295 if @best[:runtime][:value] >= prop[:runtime][:value]
300 if @best[:runtime][:value] >= prop[:runtime][:value]
296 @best[:runtime] = prop[:runtime]
301 @best[:runtime] = prop[:runtime]
297 @best[:runtime][:lang] = lang
302 @best[:runtime][:lang] = lang
298 end
303 end
299 if @best[:memory][:value] >= prop[:memory][:value]
304 if @best[:memory][:value] >= prop[:memory][:value]
300 @best[:memory] = prop[:memory]
305 @best[:memory] = prop[:memory]
301 @best[:memory][:lang] = lang
306 @best[:memory][:lang] = lang
302 end
307 end
303 if @best[:length][:value] >= prop[:length][:value]
308 if @best[:length][:value] >= prop[:length][:value]
304 @best[:length] = prop[:length]
309 @best[:length] = prop[:length]
305 @best[:length][:lang] = lang
310 @best[:length][:lang] = lang
306 end
311 end
307 if @best[:first][:value] >= prop[:first][:value]
312 if @best[:first][:value] >= prop[:first][:value]
308 @best[:first] = prop[:first]
313 @best[:first] = prop[:first]
309 @best[:first][:lang] = lang
314 @best[:first][:lang] = lang
310 end
315 end
311 end
316 end
312 end
317 end
313
318
314 - @histogram[:summary][:max] = [@histogram[:data].max,1].max
319 + #@histogram[:summary][:max] = [@histogram[:data].max,1].max
315 @summary[:attempt] = user.count
320 @summary[:attempt] = user.count
316 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
321 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
322 +
323 +
324 + #for new graph
325 + @chart_dataset = @problem.get_jschart_history.to_json.html_safe
317 end
326 end
318
327
319 def stuck #report struggling user,problem
328 def stuck #report struggling user,problem
320 # init
329 # init
321 user,problem = nil
330 user,problem = nil
322 solve = true
331 solve = true
323 tries = 0
332 tries = 0
324 @struggle = Array.new
333 @struggle = Array.new
325 record = {}
334 record = {}
326 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
335 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
327 next unless sub.problem and sub.user
336 next unless sub.problem and sub.user
328 if user != sub.user_id or problem != sub.problem_id
337 if user != sub.user_id or problem != sub.problem_id
329 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
338 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
330 record = {user: sub.user, problem: sub.problem}
339 record = {user: sub.user, problem: sub.problem}
331 user,problem = sub.user_id, sub.problem_id
340 user,problem = sub.user_id, sub.problem_id
332 solve = false
341 solve = false
333 tries = 0
342 tries = 0
334 end
343 end
335 if sub.points >= sub.problem.full_score
344 if sub.points >= sub.problem.full_score
336 solve = true
345 solve = true
337 else
346 else
338 tries += 1
347 tries += 1
339 end
348 end
340 end
349 end
341 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
350 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
342 @struggle = @struggle[0..50]
351 @struggle = @struggle[0..50]
343 end
352 end
344
353
345
354
346 def multiple_login
355 def multiple_login
347 #user with multiple IP
356 #user with multiple IP
348 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:login)
357 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:login)
349 last,count = 0,0
358 last,count = 0,0
350 first = 0
359 first = 0
351 @users = []
360 @users = []
352 raw.each do |r|
361 raw.each do |r|
353 if last != r.user.login
362 if last != r.user.login
354 count = 1
363 count = 1
355 last = r.user.login
364 last = r.user.login
356 first = r
365 first = r
357 else
366 else
358 @users << first if count == 1
367 @users << first if count == 1
359 @users << r
368 @users << r
360 count += 1
369 count += 1
361 end
370 end
362 end
371 end
363
372
364 #IP with multiple user
373 #IP with multiple user
365 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:ip_address)
374 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:ip_address)
366 last,count = 0,0
375 last,count = 0,0
367 first = 0
376 first = 0
368 @ip = []
377 @ip = []
369 raw.each do |r|
378 raw.each do |r|
370 if last != r.ip_address
379 if last != r.ip_address
371 count = 1
380 count = 1
372 last = r.ip_address
381 last = r.ip_address
373 first = r
382 first = r
374 else
383 else
375 @ip << first if count == 1
384 @ip << first if count == 1
376 @ip << r
385 @ip << r
377 count += 1
386 count += 1
378 end
387 end
379 end
388 end
380 end
389 end
381
390
382 def cheat_report
391 def cheat_report
383 date_and_time = '%Y-%m-%d %H:%M'
392 date_and_time = '%Y-%m-%d %H:%M'
384 begin
393 begin
385 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
394 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
386 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
395 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
387 rescue
396 rescue
388 @since_time = Time.zone.now.ago( 90.minutes)
397 @since_time = Time.zone.now.ago( 90.minutes)
389 end
398 end
390 begin
399 begin
391 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
400 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
392 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
401 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
393 rescue
402 rescue
394 @until_time = Time.zone.now
403 @until_time = Time.zone.now
395 end
404 end
396
405
397 #multi login
406 #multi login
398 @ml = Login.joins(:user).where("logins.created_at >= ? and logins.created_at <= ?",@since_time,@until_time).select('users.login,count(distinct ip_address) as count,users.full_name').group("users.id").having("count > 1")
407 @ml = Login.joins(:user).where("logins.created_at >= ? and logins.created_at <= ?",@since_time,@until_time).select('users.login,count(distinct ip_address) as count,users.full_name').group("users.id").having("count > 1")
399
408
400 st = <<-SQL
409 st = <<-SQL
401 SELECT l2.*
410 SELECT l2.*
402 FROM logins l2 INNER JOIN
411 FROM logins l2 INNER JOIN
403 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
412 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
404 FROM logins l
413 FROM logins l
405 INNER JOIN users u ON l.user_id = u.id
414 INNER JOIN users u ON l.user_id = u.id
406 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
415 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
407 GROUP BY u.id
416 GROUP BY u.id
408 HAVING count > 1
417 HAVING count > 1
409 ) ml ON l2.user_id = ml.id
418 ) ml ON l2.user_id = ml.id
410 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
419 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
411 UNION
420 UNION
412 SELECT l2.*
421 SELECT l2.*
@@ -1,111 +1,115
1 class SubmissionsController < ApplicationController
1 class SubmissionsController < ApplicationController
2 + before_action :set_submission, only: [:show,:download,:compiler_msg,:rejudge,:set_tag, :edit]
2 before_action :check_valid_login
3 before_action :check_valid_login
3 before_action :submission_authorization, only: [:show, :download, :edit]
4 before_action :submission_authorization, only: [:show, :download, :edit]
4 - before_action :admin_authorization, only: [:rejudge]
5 + before_action only: [:rejudge, :set_tag] do role_authorization([:ta]) end
5
6
6 # GET /submissions
7 # GET /submissions
7 # GET /submissions.json
8 # GET /submissions.json
8 # Show problem selection and user's submission of that problem
9 # Show problem selection and user's submission of that problem
9 def index
10 def index
10 @user = @current_user
11 @user = @current_user
11 @problems = @user.available_problems
12 @problems = @user.available_problems
12
13
13 if params[:problem_id]==nil
14 if params[:problem_id]==nil
14 @problem = nil
15 @problem = nil
15 @submissions = nil
16 @submissions = nil
16 else
17 else
17 @problem = Problem.find_by_id(params[:problem_id])
18 @problem = Problem.find_by_id(params[:problem_id])
18 if (@problem == nil) or (not @problem.available)
19 if (@problem == nil) or (not @problem.available)
19 redirect_to list_main_path
20 redirect_to list_main_path
20 flash[:error] = 'Authorization error: You have no right to view submissions for this problem'
21 flash[:error] = 'Authorization error: You have no right to view submissions for this problem'
21 return
22 return
22 end
23 end
23 @submissions = Submission.find_all_by_user_problem(@user.id, @problem.id).order(id: :desc)
24 @submissions = Submission.find_all_by_user_problem(@user.id, @problem.id).order(id: :desc)
24 end
25 end
25 end
26 end
26
27
27 # GET /submissions/1
28 # GET /submissions/1
28 # GET /submissions/1.json
29 # GET /submissions/1.json
29 def show
30 def show
30 - @submission = Submission.find(params[:id])
31 -
32 #log the viewing
31 #log the viewing
33 user = User.find(session[:user_id])
32 user = User.find(session[:user_id])
34 SubmissionViewLog.create(user_id: session[:user_id],submission_id: @submission.id) unless user.admin?
33 SubmissionViewLog.create(user_id: session[:user_id],submission_id: @submission.id) unless user.admin?
35
34
36 @task = @submission.task
35 @task = @submission.task
37 end
36 end
38
37
39 def download
38 def download
40 - @submission = Submission.find(params[:id])
41 send_data(@submission.source, {:filename => @submission.download_filename, :type => 'text/plain'})
39 send_data(@submission.source, {:filename => @submission.download_filename, :type => 'text/plain'})
42 end
40 end
43
41
44 def compiler_msg
42 def compiler_msg
45 - @submission = Submission.find(params[:id])
46 respond_to do |format|
43 respond_to do |format|
47 format.js
44 format.js
48 end
45 end
49 end
46 end
50
47
51 #on-site new submission on specific problem
48 #on-site new submission on specific problem
52 def direct_edit_problem
49 def direct_edit_problem
53 @problem = Problem.find(params[:problem_id])
50 @problem = Problem.find(params[:problem_id])
54 unless @current_user.can_view_problem?(@problem)
51 unless @current_user.can_view_problem?(@problem)
55 unauthorized_redirect
52 unauthorized_redirect
56 return
53 return
57 end
54 end
58 @source = ''
55 @source = ''
59 if (params[:view_latest])
56 if (params[:view_latest])
60 sub = Submission.find_last_by_user_and_problem(@current_user.id,@problem.id)
57 sub = Submission.find_last_by_user_and_problem(@current_user.id,@problem.id)
61 @source = @submission.source.to_s if @submission and @submission.source
58 @source = @submission.source.to_s if @submission and @submission.source
62 end
59 end
63 render 'edit'
60 render 'edit'
64 end
61 end
65
62
66 # GET /submissions/1/edit
63 # GET /submissions/1/edit
67 def edit
64 def edit
68 - @submission = Submission.find(params[:id])
69 @source = @submission.source.to_s
65 @source = @submission.source.to_s
70 @problem = @submission.problem
66 @problem = @submission.problem
71 @lang_id = @submission.language.id
67 @lang_id = @submission.language.id
72 end
68 end
73
69
74
70
75 def get_latest_submission_status
71 def get_latest_submission_status
76 @problem = Problem.find(params[:pid])
72 @problem = Problem.find(params[:pid])
77 @submission = Submission.find_last_by_user_and_problem(params[:uid],params[:pid])
73 @submission = Submission.find_last_by_user_and_problem(params[:uid],params[:pid])
78 respond_to do |format|
74 respond_to do |format|
79 format.js
75 format.js
80 end
76 end
81 end
77 end
82
78
83 # GET /submissions/:id/rejudge
79 # GET /submissions/:id/rejudge
84 def rejudge
80 def rejudge
85 - @submission = Submission.find(params[:id])
86 @task = @submission.task
81 @task = @submission.task
87 @task.status_inqueue! if @task
82 @task.status_inqueue! if @task
88 respond_to do |format|
83 respond_to do |format|
89 format.js
84 format.js
90 end
85 end
91 end
86 end
92
87
88 + def set_tag
89 + @submission.update(tag: params[:tag])
90 + redirect_to @submission
91 + end
92 +
93 protected
93 protected
94
94
95 def submission_authorization
95 def submission_authorization
96 #admin always has privileged
96 #admin always has privileged
97 return true if @current_user.admin?
97 return true if @current_user.admin?
98 return true if @current_user.has_role?('ta') && (['show','download'].include? action_name)
98 return true if @current_user.has_role?('ta') && (['show','download'].include? action_name)
99
99
100 sub = Submission.find(params[:id])
100 sub = Submission.find(params[:id])
101 if @current_user.available_problems.include? sub.problem
101 if @current_user.available_problems.include? sub.problem
102 return true if GraderConfiguration["right.user_view_submission"] or sub.user == @current_user
102 return true if GraderConfiguration["right.user_view_submission"] or sub.user == @current_user
103 end
103 end
104
104
105 #default to NO
105 #default to NO
106 unauthorized_redirect
106 unauthorized_redirect
107 return false
107 return false
108 end
108 end
109 +
110 + def set_submission
111 + @submission = Submission.find(params[:id])
112 + end
109
113
110
114
111 end
115 end
@@ -1,122 +1,144
1 class Problem < ActiveRecord::Base
1 class Problem < ActiveRecord::Base
2
2
3 belongs_to :description
3 belongs_to :description
4 has_and_belongs_to_many :contests, :uniq => true
4 has_and_belongs_to_many :contests, :uniq => true
5
5
6 #has_and_belongs_to_many :groups
6 #has_and_belongs_to_many :groups
7 has_many :groups_problems, class_name: 'GroupProblem'
7 has_many :groups_problems, class_name: 'GroupProblem'
8 has_many :groups, :through => :groups_problems
8 has_many :groups, :through => :groups_problems
9
9
10 has_many :problems_tags, class_name: 'ProblemTag'
10 has_many :problems_tags, class_name: 'ProblemTag'
11 has_many :tags, through: :problems_tags
11 has_many :tags, through: :problems_tags
12
12
13 has_many :test_pairs, :dependent => :delete_all
13 has_many :test_pairs, :dependent => :delete_all
14 has_many :testcases, :dependent => :destroy
14 has_many :testcases, :dependent => :destroy
15
15
16 has_many :submissions
16 has_many :submissions
17
17
18 validates_presence_of :name
18 validates_presence_of :name
19 validates_format_of :name, :with => /\A\w+\z/
19 validates_format_of :name, :with => /\A\w+\z/
20 validates_presence_of :full_name
20 validates_presence_of :full_name
21
21
22 scope :available, -> { where(available: true) }
22 scope :available, -> { where(available: true) }
23
23
24 DEFAULT_TIME_LIMIT = 1
24 DEFAULT_TIME_LIMIT = 1
25 DEFAULT_MEMORY_LIMIT = 32
25 DEFAULT_MEMORY_LIMIT = 32
26
26
27 + def get_jschart_history
28 + start = 4.month.ago.beginning_of_day
29 + start_date = start.to_date
30 + count = Submission.where(problem: self).where('submitted_at >= ?', start).group('DATE(submitted_at)').count
31 + i = 0
32 + label = []
33 + value = []
34 + while (start_date + i < Time.zone.now.to_date)
35 + if (start_date+i).day == 1
36 + #label << (start_date+i).strftime("%d %b %Y")
37 + #label << (start_date+i).strftime("%d")
38 + else
39 + #label << ' '
40 + #label << (start_date+i).strftime("%d")
41 + end
42 + label << (start_date+i).strftime("%d-%b")
43 + value << (count[start_date+i] || 0)
44 + i+=1
45 + end
46 + return {labels: label,datasets: [label:'sub',data: value, backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgb(75, 192, 192)']}
47 + end
48 +
27 def self.available_problems
49 def self.available_problems
28 available.order(date_added: :desc).order(:name)
50 available.order(date_added: :desc).order(:name)
29 #Problem.available.all(:order => "date_added DESC, name ASC")
51 #Problem.available.all(:order => "date_added DESC, name ASC")
30 end
52 end
31
53
32 def self.create_from_import_form_params(params, old_problem=nil)
54 def self.create_from_import_form_params(params, old_problem=nil)
33 org_problem = old_problem || Problem.new
55 org_problem = old_problem || Problem.new
34 import_params, problem = Problem.extract_params_and_check(params,
56 import_params, problem = Problem.extract_params_and_check(params,
35 org_problem)
57 org_problem)
36
58
37 if !problem.errors.empty?
59 if !problem.errors.empty?
38 return problem, 'Error importing'
60 return problem, 'Error importing'
39 end
61 end
40
62
41 problem.full_score = 100
63 problem.full_score = 100
42 problem.date_added = Time.new
64 problem.date_added = Time.new
43 problem.test_allowed = true
65 problem.test_allowed = true
44 problem.output_only = false
66 problem.output_only = false
45 problem.available = false
67 problem.available = false
46
68
47 if not problem.save
69 if not problem.save
48 return problem, 'Error importing'
70 return problem, 'Error importing'
49 end
71 end
50
72
51 import_to_db = params.has_key? :import_to_db
73 import_to_db = params.has_key? :import_to_db
52
74
53 importer = TestdataImporter.new(problem)
75 importer = TestdataImporter.new(problem)
54
76
55 if not importer.import_from_file(import_params[:file],
77 if not importer.import_from_file(import_params[:file],
56 import_params[:time_limit],
78 import_params[:time_limit],
57 import_params[:memory_limit],
79 import_params[:memory_limit],
58 import_params[:checker_name],
80 import_params[:checker_name],
59 import_to_db)
81 import_to_db)
60 problem.errors.add(:base,'Import error.')
82 problem.errors.add(:base,'Import error.')
61 end
83 end
62
84
63 return problem, importer.log_msg
85 return problem, importer.log_msg
64 end
86 end
65
87
66 def self.download_file_basedir
88 def self.download_file_basedir
67 return "#{Rails.root}/data/tasks"
89 return "#{Rails.root}/data/tasks"
68 end
90 end
69
91
70 def get_submission_stat
92 def get_submission_stat
71 result = Hash.new
93 result = Hash.new
72 #total number of submission
94 #total number of submission
73 result[:total_sub] = Submission.where(problem_id: self.id).count
95 result[:total_sub] = Submission.where(problem_id: self.id).count
74 result[:attempted_user] = Submission.where(problem_id: self.id).group(:user_id)
96 result[:attempted_user] = Submission.where(problem_id: self.id).group(:user_id)
75 result[:pass] = Submission.where(problem_id: self.id).where("points >= ?",self.full_score).count
97 result[:pass] = Submission.where(problem_id: self.id).where("points >= ?",self.full_score).count
76 return result
98 return result
77 end
99 end
78
100
79 def long_name
101 def long_name
80 "[#{name}] #{full_name}"
102 "[#{name}] #{full_name}"
81 end
103 end
82
104
83 protected
105 protected
84
106
85 def self.to_i_or_default(st, default)
107 def self.to_i_or_default(st, default)
86 if st!=''
108 if st!=''
87 result = st.to_i
109 result = st.to_i
88 end
110 end
89 result ||= default
111 result ||= default
90 end
112 end
91
113
92 def self.to_f_or_default(st, default)
114 def self.to_f_or_default(st, default)
93 if st!=''
115 if st!=''
94 result = st.to_f
116 result = st.to_f
95 end
117 end
96 result ||= default
118 result ||= default
97 end
119 end
98
120
99 def self.extract_params_and_check(params, problem)
121 def self.extract_params_and_check(params, problem)
100 time_limit = Problem.to_f_or_default(params[:time_limit],
122 time_limit = Problem.to_f_or_default(params[:time_limit],
101 DEFAULT_TIME_LIMIT)
123 DEFAULT_TIME_LIMIT)
102 memory_limit = Problem.to_i_or_default(params[:memory_limit],
124 memory_limit = Problem.to_i_or_default(params[:memory_limit],
103 DEFAULT_MEMORY_LIMIT)
125 DEFAULT_MEMORY_LIMIT)
104
126
105 if time_limit<=0 or time_limit >60
127 if time_limit<=0 or time_limit >60
106 problem.errors.add(:base,'Time limit out of range.')
128 problem.errors.add(:base,'Time limit out of range.')
107 end
129 end
108
130
109 if memory_limit==0 and params[:memory_limit]!='0'
131 if memory_limit==0 and params[:memory_limit]!='0'
110 problem.errors.add(:base,'Memory limit format errors.')
132 problem.errors.add(:base,'Memory limit format errors.')
111 elsif memory_limit<=0 or memory_limit >512
133 elsif memory_limit<=0 or memory_limit >512
112 problem.errors.add(:base,'Memory limit out of range.')
134 problem.errors.add(:base,'Memory limit out of range.')
113 end
135 end
114
136
115 if params[:file]==nil or params[:file]==''
137 if params[:file]==nil or params[:file]==''
116 problem.errors.add(:base,'No testdata file.')
138 problem.errors.add(:base,'No testdata file.')
117 end
139 end
118
140
119 checker_name = 'text'
141 checker_name = 'text'
120 if ['text','float'].include? params[:checker]
142 if ['text','float'].include? params[:checker]
121 checker_name = params[:checker]
143 checker_name = params[:checker]
122 end
144 end
@@ -1,168 +1,170
1 class Submission < ActiveRecord::Base
1 class Submission < ActiveRecord::Base
2
2
3 + enum tag: {default: 0, model: 1}, _prefix: true
4 +
3 belongs_to :language
5 belongs_to :language
4 belongs_to :problem
6 belongs_to :problem
5 belongs_to :user
7 belongs_to :user
6
8
7 before_validation :assign_problem
9 before_validation :assign_problem
8 before_validation :assign_language
10 before_validation :assign_language
9
11
10 validates_presence_of :source
12 validates_presence_of :source
11 validates_length_of :source, :maximum => 100_000, :allow_blank => true, :message => 'code too long, the limit is 100,000 bytes'
13 validates_length_of :source, :maximum => 100_000, :allow_blank => true, :message => 'code too long, the limit is 100,000 bytes'
12 validates_length_of :source, :minimum => 1, :allow_blank => true, :message => 'too short'
14 validates_length_of :source, :minimum => 1, :allow_blank => true, :message => 'too short'
13 validate :must_have_valid_problem
15 validate :must_have_valid_problem
14 validate :must_specify_language
16 validate :must_specify_language
15
17
16 has_one :task
18 has_one :task
17
19
18 before_save :assign_latest_number_if_new_recond
20 before_save :assign_latest_number_if_new_recond
19
21
20 def self.find_last_by_user_and_problem(user_id, problem_id)
22 def self.find_last_by_user_and_problem(user_id, problem_id)
21 where("user_id = ? AND problem_id = ?",user_id,problem_id).last
23 where("user_id = ? AND problem_id = ?",user_id,problem_id).last
22 end
24 end
23
25
24 def self.find_all_last_by_problem(problem_id)
26 def self.find_all_last_by_problem(problem_id)
25 # need to put in SQL command, maybe there's a better way
27 # need to put in SQL command, maybe there's a better way
26 Submission.includes(:user).find_by_sql("SELECT * FROM submissions " +
28 Submission.includes(:user).find_by_sql("SELECT * FROM submissions " +
27 - "WHERE id = " +
29 + "WHERE id = " +
28 - "(SELECT MAX(id) FROM submissions AS subs " +
30 + "(SELECT MAX(id) FROM submissions AS subs " +
29 - "WHERE subs.user_id = submissions.user_id AND " +
31 + "WHERE subs.user_id = submissions.user_id AND " +
30 - "problem_id = " + problem_id.to_s + " " +
32 + "problem_id = " + problem_id.to_s + " " +
31 - "GROUP BY user_id) " +
33 + "GROUP BY user_id) " +
32 - "ORDER BY user_id")
34 + "ORDER BY user_id")
33 end
35 end
34
36
35 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
37 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
36 records = Submission.where(problem_id: problem_id,user_id: user_id)
38 records = Submission.where(problem_id: problem_id,user_id: user_id)
37 records = records.where('id >= ?',since_id) if since_id and since_id > 0
39 records = records.where('id >= ?',since_id) if since_id and since_id > 0
38 records = records.where('id <= ?',until_id) if until_id and until_id > 0
40 records = records.where('id <= ?',until_id) if until_id and until_id > 0
39 records.all
41 records.all
40 end
42 end
41
43
42 def self.find_last_for_all_available_problems(user_id)
44 def self.find_last_for_all_available_problems(user_id)
43 submissions = Array.new
45 submissions = Array.new
44 problems = Problem.available_problems
46 problems = Problem.available_problems
45 problems.each do |problem|
47 problems.each do |problem|
46 sub = Submission.find_last_by_user_and_problem(user_id, problem.id)
48 sub = Submission.find_last_by_user_and_problem(user_id, problem.id)
47 submissions << sub if sub!=nil
49 submissions << sub if sub!=nil
48 end
50 end
49 submissions
51 submissions
50 end
52 end
51
53
52 def self.find_by_user_problem_number(user_id, problem_id, number)
54 def self.find_by_user_problem_number(user_id, problem_id, number)
53 where("user_id = ? AND problem_id = ? AND number = ?",user_id,problem_id,number).first
55 where("user_id = ? AND problem_id = ? AND number = ?",user_id,problem_id,number).first
54 end
56 end
55
57
56 def self.find_all_by_user_problem(user_id, problem_id)
58 def self.find_all_by_user_problem(user_id, problem_id)
57 where("user_id = ? AND problem_id = ?",user_id,problem_id)
59 where("user_id = ? AND problem_id = ?",user_id,problem_id)
58 end
60 end
59
61
60 def download_filename
62 def download_filename
61 if self.problem.output_only
63 if self.problem.output_only
62 return self.source_filename
64 return self.source_filename
63 else
65 else
64 timestamp = self.submitted_at.localtime.strftime("%H%M%S")
66 timestamp = self.submitted_at.localtime.strftime("%H%M%S")
65 return "#{self.problem.name}-#{timestamp}.#{self.language.ext}"
67 return "#{self.problem.name}-#{timestamp}.#{self.language.ext}"
66 end
68 end
67 end
69 end
68
70
69 protected
71 protected
70
72
71 def self.find_option_in_source(option, source)
73 def self.find_option_in_source(option, source)
72 if source==nil
74 if source==nil
73 return nil
75 return nil
74 end
76 end
75 i = 0
77 i = 0
76 source.each_line do |s|
78 source.each_line do |s|
77 if s =~ option
79 if s =~ option
78 - words = s.split
80 + words = s.split
79 - return words[1]
81 + return words[1]
80 end
82 end
81 i = i + 1
83 i = i + 1
82 if i==10
84 if i==10
83 - return nil
85 + return nil
84 end
86 end
85 end
87 end
86 return nil
88 return nil
87 end
89 end
88
90
89 def self.find_language_in_source(source, source_filename="")
91 def self.find_language_in_source(source, source_filename="")
90 langopt = find_option_in_source(/^LANG:/,source)
92 langopt = find_option_in_source(/^LANG:/,source)
91 if langopt
93 if langopt
92 return (Language.find_by_name(langopt) ||
94 return (Language.find_by_name(langopt) ||
93 Language.find_by_pretty_name(langopt))
95 Language.find_by_pretty_name(langopt))
94 else
96 else
95 if source_filename
97 if source_filename
96 return Language.find_by_extension(source_filename.split('.').last)
98 return Language.find_by_extension(source_filename.split('.').last)
97 else
99 else
98 return nil
100 return nil
99 end
101 end
100 end
102 end
101 end
103 end
102
104
103 def self.find_problem_in_source(source, source_filename="")
105 def self.find_problem_in_source(source, source_filename="")
104 prob_opt = find_option_in_source(/^TASK:/,source)
106 prob_opt = find_option_in_source(/^TASK:/,source)
105 if problem = Problem.find_by_name(prob_opt)
107 if problem = Problem.find_by_name(prob_opt)
106 return problem
108 return problem
107 else
109 else
108 if source_filename
110 if source_filename
109 return Problem.find_by_name(source_filename.split('.').first)
111 return Problem.find_by_name(source_filename.split('.').first)
110 else
112 else
111 return nil
113 return nil
112 end
114 end
113 end
115 end
114 end
116 end
115
117
116 def assign_problem
118 def assign_problem
117 if self.problem_id!=-1
119 if self.problem_id!=-1
118 begin
120 begin
119 self.problem = Problem.find(self.problem_id)
121 self.problem = Problem.find(self.problem_id)
120 rescue ActiveRecord::RecordNotFound
122 rescue ActiveRecord::RecordNotFound
121 self.problem = nil
123 self.problem = nil
122 end
124 end
123 else
125 else
124 self.problem = Submission.find_problem_in_source(self.source,
126 self.problem = Submission.find_problem_in_source(self.source,
125 self.source_filename)
127 self.source_filename)
126 end
128 end
127 end
129 end
128
130
129 def assign_language
131 def assign_language
130 if self.language == nil
132 if self.language == nil
131 self.language = Submission.find_language_in_source(self.source,
133 self.language = Submission.find_language_in_source(self.source,
132 self.source_filename)
134 self.source_filename)
133 end
135 end
134 end
136 end
135
137
136 # validation codes
138 # validation codes
137 def must_specify_language
139 def must_specify_language
138 return if self.source==nil
140 return if self.source==nil
139
141
140 # for output_only tasks
142 # for output_only tasks
141 return if self.problem!=nil and self.problem.output_only
143 return if self.problem!=nil and self.problem.output_only
142
144
143 if self.language == nil
145 if self.language == nil
144 errors.add('source',"Cannot detect language. Did you submit a correct source file?")
146 errors.add('source',"Cannot detect language. Did you submit a correct source file?")
145 end
147 end
146 end
148 end
147
149
148 def must_have_valid_problem
150 def must_have_valid_problem
149 return if self.source==nil
151 return if self.source==nil
150 if self.problem==nil
152 if self.problem==nil
151 errors.add('problem',"must be specified.")
153 errors.add('problem',"must be specified.")
152 else
154 else
153 #admin always have right
155 #admin always have right
154 return if self.user.admin?
156 return if self.user.admin?
155
157
156 #check if user has the right to submit the problem
158 #check if user has the right to submit the problem
157 errors[:base] << "Authorization error: you have no right to submit to this problem" if (!self.user.available_problems.include?(self.problem)) and (self.new_record?)
159 errors[:base] << "Authorization error: you have no right to submit to this problem" if (!self.user.available_problems.include?(self.problem)) and (self.new_record?)
158 end
160 end
159 end
161 end
160
162
161 # callbacks
163 # callbacks
162 def assign_latest_number_if_new_recond
164 def assign_latest_number_if_new_recond
163 return if !self.new_record?
165 return if !self.new_record?
164 latest = Submission.find_last_by_user_and_problem(self.user_id, self.problem_id)
166 latest = Submission.find_last_by_user_and_problem(self.user_id, self.problem_id)
165 self.number = (latest==nil) ? 1 : latest.number + 1;
167 self.number = (latest==nil) ? 1 : latest.number + 1;
166 end
168 end
167
169
168 end
170 end
@@ -1,136 +1,154
1 :css
1 :css
2 .hof_user { color: orangered; font-style: italic; }
2 .hof_user { color: orangered; font-style: italic; }
3 .hof_language { color: green; font-style: italic; }
3 .hof_language { color: green; font-style: italic; }
4 .hof_value { color: deeppink;font-style: italic; }
4 .hof_value { color: deeppink;font-style: italic; }
5 .info_param { font-weight: bold;text-align: right; }
5 .info_param { font-weight: bold;text-align: right; }
6 .tooltip {
6 .tooltip {
7 font-family: Verdana,sans-serif;
7 font-family: Verdana,sans-serif;
8 font-weight: normal;
8 font-weight: normal;
9 text-align: left;
9 text-align: left;
10 font-size: 1.0em;
10 font-size: 1.0em;
11 color: black;
11 color: black;
12 line-height: 1.1;
12 line-height: 1.1;
13 display: none;
13 display: none;
14 min-width: 20em;
14 min-width: 20em;
15 position: absolute;
15 position: absolute;
16 left: 25px;
16 left: 25px;
17 bottom: 5px;
17 bottom: 5px;
18 border: 1px solid;
18 border: 1px solid;
19 padding: 5px;
19 padding: 5px;
20 background-color: #FFF;
20 background-color: #FFF;
21 word-wrap: break-word;
21 word-wrap: break-word;
22 z-index: 9999;
22 z-index: 9999;
23 overflow: auto;
23 overflow: auto;
24 }
24 }
25
25
26
26
27 - .container
27 + .container-fluid
28 + .row
29 + .col-md-8
30 + .card
31 + .card-body
32 + %h2.card-title Submission History
33 + %canvas#chart{height: '50px'}
34 +
35 + .col-md-4
36 + .card
37 + .card-body
38 + %h2.card-title General Info
39 + .row
40 + .col-sm-6
41 + Subs
42 + .col-sm-6
43 + = @summary[:count]
44 + .row
45 + .col-sm-6
46 + Solved/Attempted User
47 + .col-sm-6
48 + #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
28 .row
49 .row
29 .col-md-4
50 .col-md-4
30 - %h2 Overall Stat
51 + .card
31 - %table.table.table-hover
52 + .card-body
32 - %thead
53 + %h2.card-title Model submission
33 - %tr
34 - %th
35 - %th
36 - %tbody
37 - %tr
38 - %td.info_param Submissions
39 - %td= @summary[:count]
40 - %tr
41 - %td.info_param Solved/Attempted User
42 - %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
43 - - if @best
44 - %tr
45 - %td.info_param Best Runtime
46 - %td
47 - by #{link_to @best[:runtime][:user], stat_user_path(@best[:runtime][:user_id])}
48 - %br
49 - using <span class="text-success">#{@best[:runtime][:lang]}</span>
50 - %br
51 - with <span class="text-success">#{@best[:runtime][:value] * 1000} milliseconds</span>
52 - %br
53 - at submission
54 - = link_to "#" + @best[:runtime][:sub_id].to_s, submission_path(@best[:runtime][:sub_id])
55 -
56 - %tr
57 - %td.info_param
58 - Best Memory Usage
59 - %sup{ id: "xmem_remark",
60 - style: "position:relative; color: blue;",
61 - data: {toggle: 'tooltip', placement: 'top', animation: 'false', delay: 20},
62 - title: "This counts only for submission with 100% score. Right now, java is excluded from memory usage competition. (Because it always uses 2GB memory...)"}
63 - [?]
64 - %td
65 - by #{link_to @best[:memory][:user], stat_user_path(@best[:memory][:user_id])}
66 - %br
67 - using <span class="text-success">#{@best[:memory][:lang]}</span>
68 - %br
69 - with <span class="text-success">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
70 - %br
71 - at submission
72 - = link_to "#" + @best[:memory][:sub_id].to_s, submission_path(@best[:memory][:sub_id])
73 -
74 - %tr
75 - %td.info_param Shortest Code
76 - %td
77 - by #{link_to @best[:length][:user], stat_user_path(@best[:length][:user_id])}
78 - %br
79 - using <span class="text-success">#{@best[:length][:lang]}</span>
80 - %br
81 - with <span class="text-success">#{@best[:length][:value]} bytes</span>
82 - %br
83 - at submission
84 - = link_to "#" + @best[:length][:sub_id].to_s, submission_path(@best[:length][:sub_id])
85 -
86 - %tr
87 - %td.info_param First solver
88 - %td
89 - - if @best[:first][:user] != '(NULL)'
90 - #{link_to @best[:first][:user], stat_user_path(@best[:first][:user_id])} is the first solver
91 - %br
92 - using <span class="text-success">#{@best[:first][:lang]}</span>
93 - %br
94 - on <span class="text-success">#{@best[:first][:value]}</span>
95 - %br
96 - at submission
97 - = link_to "#" + @best[:first][:sub_id].to_s, submission_path( @best[:first][:sub_id])
98 - - else
99 - no first solver
100 - .col-md-8
101 - - if @best
102 - %h2 By Language
103 %table.table.table-hover
54 %table.table.table-hover
104 %thead
55 %thead
105 %tr
56 %tr
106 - %th Language
57 + %th #Sub
107 - %th Best runtime (ms)
58 + %th Author
108 - %th Best memory (kbytes)
109 - %th Shortest Code (bytes)
110 - %th First solver
111 %tbody
59 %tbody
112 - - @by_lang.each do |lang,value|
60 + - @model_subs.each do |sub|
113 %tr
61 %tr
114 - %td= lang
62 + %td= link_to "##{sub.id}", submission_path(sub)
63 + %td= sub.user.full_name
64 + .col-md-8
65 + - if @best
66 + .card
67 + .card-body
68 + %h2.card-title Top Submissions
69 + %table.table.table-hover
70 + %thead
71 + %tr
72 + %th Language
73 + %th Best runtime (ms)
74 + %th Best memory (kbytes)
75 + %th Shortest Code (bytes)
76 + %th First solver
77 + %tbody
78 + %tr.bg-warning
115 %td
79 %td
116 - = link_to value[:runtime][:user], stat_user_path(value[:runtime][:user_id])
80 + Overall
81 + %td
82 + by #{link_to @best[:runtime][:user], stat_user_path(@best[:runtime][:user_id])}
83 + %br
84 + using <span class="text-success">#{@best[:runtime][:lang]}</span>
85 + %br
86 + with <span class="text-success">#{@best[:runtime][:value] * 1000} milliseconds</span>
87 + %br= link_to "#" + @best[:runtime][:sub_id].to_s, submission_path(@best[:runtime][:sub_id])
88 + %td
89 + by #{link_to @best[:memory][:user], stat_user_path(@best[:memory][:user_id])}
117 %br
90 %br
118 - = "#{(value[:runtime][:value] * 1000).to_i} @"
91 + using <span class="text-success">#{@best[:memory][:lang]}</span>
119 - = link_to "#" + value[:runtime][:sub_id].to_s, submission_path( value[:runtime][:sub_id])
92 + %br
93 + with <span class="text-success">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
94 + %br= link_to "#" + @best[:memory][:sub_id].to_s, submission_path(@best[:memory][:sub_id])
95 + %td
96 + by #{link_to @best[:length][:user], stat_user_path(@best[:length][:user_id])}
97 + %br
98 + using <span class="text-success">#{@best[:length][:lang]}</span>
99 + %br
100 + with <span class="text-success">#{@best[:length][:value]} bytes</span>
101 + %br= link_to "#" + @best[:length][:sub_id].to_s, submission_path(@best[:length][:sub_id])
120 %td
102 %td
121 - = link_to value[:memory][:user], stat_user_path( value[:memory][:user_id])
103 + - if @best[:first][:user] != '(NULL)'
122 - %br
104 + #{link_to @best[:first][:user], stat_user_path(@best[:first][:user_id])} is the first solver
123 - = "#{number_with_delimiter(value[:memory][:value])} @"
105 + %br
124 - = link_to "#" + value[:memory][:sub_id].to_s, submission_path(value[:memory][:sub_id])
106 + using <span class="text-success">#{@best[:first][:lang]}</span>
125 - %td
107 + %br
126 - = link_to value[:length][:user], stat_user_path(value[:length][:user_id])
108 + on <span class="text-success">#{@best[:first][:value]}</span>
127 - %br
109 + %br= link_to "#" + @best[:first][:sub_id].to_s, submission_path( @best[:first][:sub_id])
128 - = "#{value[:length][:value]} @"
110 + - else
129 - = link_to "#" + value[:length][:sub_id].to_s, submission_path(value[:length][:sub_id])
111 + no first solver
130 - %td
112 + - @by_lang.each do |lang,value|
131 - - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
113 + %tr
132 - = link_to value[:first][:user], stat_user_path(value[:first][:user_id])
114 + %td= lang
115 + %td
116 + = link_to value[:runtime][:user], stat_user_path(value[:runtime][:user_id])
117 + %br
118 + = "#{(value[:runtime][:value] * 1000).to_i} @"
119 + = link_to "#" + value[:runtime][:sub_id].to_s, submission_path( value[:runtime][:sub_id])
120 + %td
121 + = link_to value[:memory][:user], stat_user_path( value[:memory][:user_id])
122 + %br
123 + = "#{number_with_delimiter(value[:memory][:value])} @"
124 + = link_to "#" + value[:memory][:sub_id].to_s, submission_path(value[:memory][:sub_id])
125 + %td
126 + = link_to value[:length][:user], stat_user_path(value[:length][:user_id])
133 %br
127 %br
134 - = "#{value[:first][:value]} @"
128 + = "#{value[:length][:value]} @"
135 - = link_to "#" + value[:first][:sub_id].to_s, submission_path( value[:first][:sub_id])
129 + = link_to "#" + value[:length][:sub_id].to_s, submission_path(value[:length][:sub_id])
130 + %td
131 + - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
132 + = link_to value[:first][:user], stat_user_path(value[:first][:user_id])
133 + %br
134 + = "#{value[:first][:value]} @"
135 + = link_to "#" + value[:first][:sub_id].to_s, submission_path( value[:first][:sub_id])
136
136
137 + %script{src:"https://cdn.jsdelivr.net/npm/chart.js"}
138 + :javascript
139 + data = #{@chart_dataset}
140 + config = {
141 + type: 'bar',
142 + data: data,
143 + options: {
144 + plugins: {
145 + legend: {
146 + display: false
147 + },
148 + },
149 + }
150 + }
151 + Chart.defaults.font.size = 15
152 + //Chart.defaults.font.family = 'Sarabun Light'
153 + chart = new Chart($('#chart'),config)
154 +
@@ -1,29 +1,29
1
1
2 /- if params[:id]
2 /- if params[:id]
3 / %h1 Tasks Hall of Fame
3 / %h1 Tasks Hall of Fame
4 / = link_to('[back to All-Time Hall of Fame]', action: 'problem_hof', id: nil )
4 / = link_to('[back to All-Time Hall of Fame]', action: 'problem_hof', id: nil )
5 /- else
5 /- else
6 / %h1 All-Time Hall of Fame
6 / %h1 All-Time Hall of Fame
7
7
8 .panel.panel-info
8 .panel.panel-info
9 .panel-heading
9 .panel-heading
10 Select Task
10 Select Task
11 .panel-body
11 .panel-body
12 .form-inline
12 .form-inline
13 = select 'report',
13 = select 'report',
14 'problem_id',
14 'problem_id',
15 @problems.collect {|p| ["[#{p.name}] #{p.full_name}", problem_hof_report_path(p)]},
15 @problems.collect {|p| ["[#{p.name}] #{p.full_name}", problem_hof_report_path(p)]},
16 {:selected => problem_hof_report_path(@problem)},
16 {:selected => problem_hof_report_path(@problem)},
17 { class: 'select2 form-control' }
17 { class: 'select2 form-control' }
18 %button.btn.btn-primary.btn-sm.go-button#problem_go{data: {source: "#report_problem_id"}} Go
18 %button.btn.btn-primary.btn-sm.go-button#problem_go{data: {source: "#report_problem_id"}} Go
19
19
20
20
21 - unless params[:id]
21 - unless params[:id]
22 /=render partial: 'all_time_hof'
22 /=render partial: 'all_time_hof'
23 Please select a problem.
23 Please select a problem.
24 - else
24 - else
25 %h1 [#{Problem.find(params[:id]).name}] #{Problem.find(params[:id]).full_name}
25 %h1 [#{Problem.find(params[:id]).name}] #{Problem.find(params[:id]).full_name}
26 - %h2 Submission History
26 + -# %h2 Submission History
27 - =render partial: 'application/bar_graph', locals: { histogram: @histogram }
27 + -# =render partial: 'application/bar_graph', locals: { histogram: @histogram }
28 =render partial: 'task_hof'
28 =render partial: 'task_hof'
29
29
@@ -1,116 +1,128
1 %h1= "Submission: #{@submission.id}"
1 %h1= "Submission: #{@submission.id}"
2
2
3 %textarea#data{style: "display:none;"}
3 %textarea#data{style: "display:none;"}
4 :preserve
4 :preserve
5 #{@submission.source}
5 #{@submission.source}
6
6
7 //%div.highlight{:style => "border: 1px solid black;"}
7 //%div.highlight{:style => "border: 1px solid black;"}
8 //=@formatted_code.html_safe
8 //=@formatted_code.html_safe
9
9
10
10
11 .containter
11 .containter
12 .row
12 .row
13 .col-md-7
13 .col-md-7
14 %h2 Source Code
14 %h2 Source Code
15 .col-md-5
15 .col-md-5
16 %h2 Stat
16 %h2 Stat
17 .row
17 .row
18 .col-md-7
18 .col-md-7
19 %div#editor{ style: "font-size: 14px; height: 400px; border-radius:5px;" }
19 %div#editor{ style: "font-size: 14px; height: 400px; border-radius:5px;" }
20 :javascript
20 :javascript
21 e = ace.edit("editor")
21 e = ace.edit("editor")
22 e.setOptions({ maxLines: Infinity })
22 e.setOptions({ maxLines: Infinity })
23 e.setValue($("#data").text())
23 e.setValue($("#data").text())
24 e.gotoLine(1)
24 e.gotoLine(1)
25 e.getSession().setMode("#{get_ace_mode(@submission.language)}")
25 e.getSession().setMode("#{get_ace_mode(@submission.language)}")
26 e.setReadOnly(true)
26 e.setReadOnly(true)
27 .col-md-5
27 .col-md-5
28 %table.table.table-striped
28 %table.table.table-striped
29 %tr
29 %tr
30 %td.text-right
30 %td.text-right
31 %strong User
31 %strong User
32 %td
32 %td
33 - if @current_user.admin? ||@current_user == @submission.user
33 - if @current_user.admin? ||@current_user == @submission.user
34 - if @submission.user
34 - if @submission.user
35 = link_to "#{@submission.user.login}", stat_user_path(@submission.user)
35 = link_to "#{@submission.user.login}", stat_user_path(@submission.user)
36 = @submission.user.full_name
36 = @submission.user.full_name
37 - else
37 - else
38 = "(n/a)"
38 = "(n/a)"
39 - else
39 - else
40 = '-- REDACTED --'
40 = '-- REDACTED --'
41 %tr
41 %tr
42 %td.text-right
42 %td.text-right
43 %strong Task
43 %strong Task
44 %td
44 %td
45 - if @submission.problem!=nil
45 - if @submission.problem!=nil
46 = link_to "[#{@submission.problem.name}]", stat_problem_path(@submission.problem)
46 = link_to "[#{@submission.problem.name}]", stat_problem_path(@submission.problem)
47 = @submission.problem.full_name
47 = @submission.problem.full_name
48 = link_to_description_if_any "[download] <span class='glyphicon glyphicon-file'></span>".html_safe, @submission.problem
48 = link_to_description_if_any "[download] <span class='glyphicon glyphicon-file'></span>".html_safe, @submission.problem
49 - else
49 - else
50 = "(n/a)"
50 = "(n/a)"
51 %tr
51 %tr
52 %td.text-right
52 %td.text-right
53 %strong Tries
53 %strong Tries
54 %td= @submission.number
54 %td= @submission.number
55 %tr
55 %tr
56 %td.text-right
56 %td.text-right
57 %strong Language
57 %strong Language
58 %td= @submission.language.pretty_name
58 %td= @submission.language.pretty_name
59 %tr
59 %tr
60 %td.text-right
60 %td.text-right
61 %strong Submitted
61 %strong Submitted
62 %td #{time_ago_in_words(@submission.submitted_at)} ago (at #{@submission.submitted_at.to_formatted_s(:long)})
62 %td #{time_ago_in_words(@submission.submitted_at)} ago (at #{@submission.submitted_at.to_formatted_s(:long)})
63 %tr
63 %tr
64 %td.text-right
64 %td.text-right
65 %strong Graded
65 %strong Graded
66 - if @submission.graded_at
66 - if @submission.graded_at
67 %td #{time_ago_in_words(@submission.graded_at)} ago (at #{@submission.graded_at.to_formatted_s(:long)})
67 %td #{time_ago_in_words(@submission.graded_at)} ago (at #{@submission.graded_at.to_formatted_s(:long)})
68 - else
68 - else
69 %td -
69 %td -
70 %tr
70 %tr
71 %td.text-right
71 %td.text-right
72 %strong Points
72 %strong Points
73 %td #{@submission.points}/#{@submission.try(:problem).try(:full_score)}
73 %td #{@submission.points}/#{@submission.try(:problem).try(:full_score)}
74 %tr
74 %tr
75 %td.text-right
75 %td.text-right
76 %strong Comment
76 %strong Comment
77 %td #{@submission.grader_comment}
77 %td #{@submission.grader_comment}
78 %tr
78 %tr
79 %td.text-right
79 %td.text-right
80 %strong Runtime (s)
80 %strong Runtime (s)
81 %td #{@submission.max_runtime}
81 %td #{@submission.max_runtime}
82 %tr
82 %tr
83 %td.text-right
83 %td.text-right
84 %strong Memory (kb)
84 %strong Memory (kb)
85 %td #{@submission.peak_memory}
85 %td #{@submission.peak_memory}
86 %tr
86 %tr
87 %td.text-right
87 %td.text-right
88 %strong Compiler result
88 %strong Compiler result
89 %td
89 %td
90 %button.btn.btn-info.btn-xs{type: 'button', data: {toggle: 'modal', target: '#compiler'}}
90 %button.btn.btn-info.btn-xs{type: 'button', data: {toggle: 'modal', target: '#compiler'}}
91 view
91 view
92 - - if session[:admin]
93 - %tr
94 - %td.text-right
95 - %strong IP
96 - %td #{@submission.ip_address}
97 %tr
92 %tr
98 %td.text-right
93 %td.text-right
99 %strong Grading Task Status
94 %strong Grading Task Status
100 %td
95 %td
101 = @task.status_str if @task
96 = @task.status_str if @task
102 - if session[:admin]
97 - if session[:admin]
103 = link_to "rejudge", rejudge_submission_path, data: {remote: true}, class: 'btn btn-info btn-xs'
98 = link_to "rejudge", rejudge_submission_path, data: {remote: true}, class: 'btn btn-info btn-xs'
99 + - if session[:admin]
100 + %tr
101 + %td.text-right
102 + %strong IP
103 + %td #{@submission.ip_address}
104 + %tr
105 + %td.text-right
106 + %strong Model solution
107 + %td
108 + - if @submission.tag_model?
109 + YES
110 + - if session[:admin]
111 + = link_to "remove model status", set_tag_submission_path(@submission, tag: :default), class: 'btn btn-warning btn-xs'
112 + - else
113 + No
114 + - if session[:admin]
115 + = link_to "set as model solution", set_tag_submission_path(@submission, tag: :model), class: 'btn btn-success btn-xs'
104
116
105
117
106 .modal.fade#compiler{tabindex: -1,role: 'dialog'}
118 .modal.fade#compiler{tabindex: -1,role: 'dialog'}
107 .modal-dialog.modal-lg{role:'document'}
119 .modal-dialog.modal-lg{role:'document'}
108 .modal-content
120 .modal-content
109 .modal-header
121 .modal-header
110 %button.close{type: 'button', data: {dismissed: :modal}, aria: {label: 'close'}}
122 %button.close{type: 'button', data: {dismissed: :modal}, aria: {label: 'close'}}
111 %span{aria: {hidden: 'true'}, data: {dismiss: 'modal'}} &times;
123 %span{aria: {hidden: 'true'}, data: {dismiss: 'modal'}} &times;
112 %h4 Compiler message
124 %h4 Compiler message
113 .modal-body
125 .modal-body
114 %pre#compiler_msg= @submission.compiler_message
126 %pre#compiler_msg= @submission.compiler_message
115 .modal-footer
127 .modal-footer
116 %button.btn.btn-default{type: 'button', data: {dismiss: 'modal'}} Close
128 %button.btn.btn-default{type: 'button', data: {dismiss: 'modal'}} Close
@@ -1,192 +1,193
1 Rails.application.routes.draw do
1 Rails.application.routes.draw do
2 resources :tags
2 resources :tags
3 get "sources/direct_edit"
3 get "sources/direct_edit"
4
4
5 root :to => 'main#login'
5 root :to => 'main#login'
6
6
7 #logins
7 #logins
8 match 'login/login', to: 'login#login', via: [:get,:post]
8 match 'login/login', to: 'login#login', via: [:get,:post]
9
9
10 resources :contests
10 resources :contests
11 resources :sites
11 resources :sites
12 resources :test
12 resources :test
13
13
14 resources :messages do
14 resources :messages do
15 member do
15 member do
16 get 'hide'
16 get 'hide'
17 post 'reply'
17 post 'reply'
18 end
18 end
19 collection do
19 collection do
20 get 'console'
20 get 'console'
21 get 'list_all'
21 get 'list_all'
22 end
22 end
23 end
23 end
24
24
25 resources :announcements do
25 resources :announcements do
26 member do
26 member do
27 get 'toggle','toggle_front'
27 get 'toggle','toggle_front'
28 end
28 end
29 end
29 end
30
30
31 resources :problems do
31 resources :problems do
32 member do
32 member do
33 get 'toggle'
33 get 'toggle'
34 get 'toggle_test'
34 get 'toggle_test'
35 get 'toggle_view_testcase'
35 get 'toggle_view_testcase'
36 get 'stat'
36 get 'stat'
37 end
37 end
38 collection do
38 collection do
39 get 'turn_all_off'
39 get 'turn_all_off'
40 get 'turn_all_on'
40 get 'turn_all_on'
41 get 'import'
41 get 'import'
42 get 'manage'
42 get 'manage'
43 get 'quick_create'
43 get 'quick_create'
44 post 'do_manage'
44 post 'do_manage'
45 post 'do_import'
45 post 'do_import'
46 end
46 end
47 end
47 end
48
48
49 resources :groups do
49 resources :groups do
50 member do
50 member do
51 post 'add_user', to: 'groups#add_user', as: 'add_user'
51 post 'add_user', to: 'groups#add_user', as: 'add_user'
52 delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user'
52 delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user'
53 delete 'remove_all_user', to: 'groups#remove_all_user', as: 'remove_all_user'
53 delete 'remove_all_user', to: 'groups#remove_all_user', as: 'remove_all_user'
54 post 'add_problem', to: 'groups#add_problem', as: 'add_problem'
54 post 'add_problem', to: 'groups#add_problem', as: 'add_problem'
55 delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem'
55 delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem'
56 delete 'remove_all_problem', to: 'groups#remove_all_problem', as: 'remove_all_problem'
56 delete 'remove_all_problem', to: 'groups#remove_all_problem', as: 'remove_all_problem'
57 get 'toggle'
57 get 'toggle'
58 end
58 end
59 collection do
59 collection do
60
60
61 end
61 end
62 end
62 end
63
63
64 resources :testcases, only: [] do
64 resources :testcases, only: [] do
65 member do
65 member do
66 get 'download_input'
66 get 'download_input'
67 get 'download_sol'
67 get 'download_sol'
68 end
68 end
69 collection do
69 collection do
70 get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem'
70 get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem'
71 end
71 end
72 end
72 end
73
73
74 resources :grader_configuration, controller: 'configurations' do
74 resources :grader_configuration, controller: 'configurations' do
75 collection do
75 collection do
76 get 'set_exam_right(/:value)', action: 'set_exam_right', as: 'set_exam_right'
76 get 'set_exam_right(/:value)', action: 'set_exam_right', as: 'set_exam_right'
77 end
77 end
78 end
78 end
79
79
80 resources :users do
80 resources :users do
81 member do
81 member do
82 get 'toggle_activate', 'toggle_enable'
82 get 'toggle_activate', 'toggle_enable'
83 get 'stat'
83 get 'stat'
84 end
84 end
85 collection do
85 collection do
86 get 'profile'
86 get 'profile'
87 post 'chg_passwd'
87 post 'chg_passwd'
88 post 'chg_default_language'
88 post 'chg_default_language'
89 end
89 end
90 end
90 end
91
91
92 resources :submissions do
92 resources :submissions do
93 member do
93 member do
94 get 'download'
94 get 'download'
95 get 'compiler_msg'
95 get 'compiler_msg'
96 get 'rejudge'
96 get 'rejudge'
97 + get 'set_tag'
97 end
98 end
98 collection do
99 collection do
99 get 'prob/:problem_id', to: 'submissions#index', as: 'problem'
100 get 'prob/:problem_id', to: 'submissions#index', as: 'problem'
100 get 'direct_edit_problem/:problem_id(/:user_id)', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
101 get 'direct_edit_problem/:problem_id(/:user_id)', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
101 get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status'
102 get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status'
102 end
103 end
103 end
104 end
104
105
105
106
106 #user admin
107 #user admin
107 resources :user_admin do
108 resources :user_admin do
108 collection do
109 collection do
109 match 'bulk_manage', via: [:get, :post]
110 match 'bulk_manage', via: [:get, :post]
110 get 'bulk_mail'
111 get 'bulk_mail'
111 get 'user_stat'
112 get 'user_stat'
112 get 'import'
113 get 'import'
113 get 'new_list'
114 get 'new_list'
114 get 'admin'
115 get 'admin'
115 get 'active'
116 get 'active'
116 get 'mass_mailing'
117 get 'mass_mailing'
117 match 'modify_role', via: [:get, :post]
118 match 'modify_role', via: [:get, :post]
118 match 'create_from_list', via: [:get, :post]
119 match 'create_from_list', via: [:get, :post]
119 match 'random_all_passwords', via: [:get, :post]
120 match 'random_all_passwords', via: [:get, :post]
120 end
121 end
121 member do
122 member do
122 get 'clear_last_ip'
123 get 'clear_last_ip'
123 end
124 end
124 end
125 end
125
126
126 resources :contest_management, only: [:index] do
127 resources :contest_management, only: [:index] do
127 collection do
128 collection do
128 get 'user_stat'
129 get 'user_stat'
129 get 'clear_stat'
130 get 'clear_stat'
130 get 'clear_all_stat'
131 get 'clear_all_stat'
131 get 'change_contest_mode'
132 get 'change_contest_mode'
132 end
133 end
133 end
134 end
134
135
135 #get 'user_admin', to: 'user_admin#index'
136 #get 'user_admin', to: 'user_admin#index'
136 #get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin'
137 #get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin'
137 #post 'user_admin', to: 'user_admin#create'
138 #post 'user_admin', to: 'user_admin#create'
138 #delete 'user_admin/:id', to: 'user_admin#destroy', as: 'user_admin_destroy'
139 #delete 'user_admin/:id', to: 'user_admin#destroy', as: 'user_admin_destroy'
139
140
140 #singular resource
141 #singular resource
141 #---- BEWARE ---- singular resource maps to plural controller by default, we can override by provide controller name directly
142 #---- BEWARE ---- singular resource maps to plural controller by default, we can override by provide controller name directly
142 #report
143 #report
143 resource :report, only: [], controller: 'report' do
144 resource :report, only: [], controller: 'report' do
144 get 'login'
145 get 'login'
145 get 'multiple_login'
146 get 'multiple_login'
146 get 'problem_hof(/:id)', action: 'problem_hof', as: 'problem_hof'
147 get 'problem_hof(/:id)', action: 'problem_hof', as: 'problem_hof'
147 get 'current_score(/:group_id)', action: 'current_score', as: 'current_score'
148 get 'current_score(/:group_id)', action: 'current_score', as: 'current_score'
148 get 'max_score'
149 get 'max_score'
149 post 'show_max_score'
150 post 'show_max_score'
150 get 'stuck'
151 get 'stuck'
151 get 'cheat_report'
152 get 'cheat_report'
152 post 'cheat_report'
153 post 'cheat_report'
153 get 'cheat_scrutinize'
154 get 'cheat_scrutinize'
154 post 'cheat_scrutinize'
155 post 'cheat_scrutinize'
155 get 'submission'
156 get 'submission'
156 post 'submission_query'
157 post 'submission_query'
157 get 'login_stat'
158 get 'login_stat'
158 post 'login_stat'
159 post 'login_stat'
159 get 'login'
160 get 'login'
160 post 'login_summary_query'
161 post 'login_summary_query'
161 post 'login_detail_query'
162 post 'login_detail_query'
162 end
163 end
163 #get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
164 #get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
164 #get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
165 #get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
165 #get "report/login"
166 #get "report/login"
166 #get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
167 #get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
167 #post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score'
168 #post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score'
168
169
169 resource :main, only: [], controller: 'main' do
170 resource :main, only: [], controller: 'main' do
170 get 'login'
171 get 'login'
171 get 'logout'
172 get 'logout'
172 get 'list'
173 get 'list'
173 get 'submission(/:id)', action: 'submission', as: 'main_submission'
174 get 'submission(/:id)', action: 'submission', as: 'main_submission'
174 get 'announcements'
175 get 'announcements'
175 get 'help'
176 get 'help'
176 post 'submit'
177 post 'submit'
177 end
178 end
178 #main
179 #main
179 #get "main/list"
180 #get "main/list"
180 #get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission'
181 #get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission'
181 #post 'main/submit', to: 'main#submit'
182 #post 'main/submit', to: 'main#submit'
182 #get 'main/announcements', to: 'main#announcements'
183 #get 'main/announcements', to: 'main#announcements'
183
184
184
185
185 #
186 #
186 get 'tasks/view/:file.:ext' => 'tasks#view'
187 get 'tasks/view/:file.:ext' => 'tasks#view'
187 get 'tasks/download/:id/:file.:ext' => 'tasks#download', as: 'download_task'
188 get 'tasks/download/:id/:file.:ext' => 'tasks#download', as: 'download_task'
188 get 'heartbeat/:id/edit' => 'heartbeat#edit'
189 get 'heartbeat/:id/edit' => 'heartbeat#edit'
189
190
190 #grader
191 #grader
191 #get 'graders/list', to: 'graders#list', as: 'grader_list'
192 #get 'graders/list', to: 'graders#list', as: 'grader_list'
192 namespace :graders do
193 namespace :graders do
@@ -1,313 +1,315
1 # This file is auto-generated from the current state of the database. Instead
1 # This file is auto-generated from the current state of the database. Instead
2 # of editing this file, please use the migrations feature of Active Record to
2 # of editing this file, please use the migrations feature of Active Record to
3 # incrementally modify your database, and then regenerate this schema definition.
3 # incrementally modify your database, and then regenerate this schema definition.
4 #
4 #
5 - # Note that this schema.rb definition is the authoritative source for your
5 + # This file is the source Rails uses to define your schema when running `bin/rails
6 - # database schema. If you need to create the application database on another
6 + # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7 - # system, you should be using db:schema:load, not running all the migrations
7 + # be faster and is potentially less error prone than running all of your
8 - # from scratch. The latter is a flawed and unsustainable approach (the more migrations
8 + # migrations from scratch. Old migrations may fail to apply correctly if those
9 - # you'll amass, the slower it'll run and the greater likelihood for issues).
9 + # migrations use external dependencies or application code.
10 #
10 #
11 # It's strongly recommended that you check this file into your version control system.
11 # It's strongly recommended that you check this file into your version control system.
12
12
13 - ActiveRecord::Schema.define(version: 2021_08_09_105935) do
13 + ActiveRecord::Schema.define(version: 2022_02_04_080936) do
14
14
15 - create_table "announcements", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
15 + create_table "announcements", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
16 t.string "author"
16 t.string "author"
17 - t.text "body", limit: 16777215
17 + t.text "body"
18 t.boolean "published"
18 t.boolean "published"
19 t.datetime "created_at", null: false
19 t.datetime "created_at", null: false
20 t.datetime "updated_at", null: false
20 t.datetime "updated_at", null: false
21 t.boolean "frontpage", default: false
21 t.boolean "frontpage", default: false
22 t.boolean "contest_only", default: false
22 t.boolean "contest_only", default: false
23 t.string "title"
23 t.string "title"
24 t.string "notes"
24 t.string "notes"
25 t.boolean "on_nav_bar", default: false
25 t.boolean "on_nav_bar", default: false
26 end
26 end
27
27
28 - create_table "contests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
28 + create_table "contests", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
29 t.string "title"
29 t.string "title"
30 t.boolean "enabled"
30 t.boolean "enabled"
31 t.datetime "created_at", null: false
31 t.datetime "created_at", null: false
32 t.datetime "updated_at", null: false
32 t.datetime "updated_at", null: false
33 t.string "name"
33 t.string "name"
34 end
34 end
35
35
36 - create_table "contests_problems", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
36 + create_table "contests_problems", id: false, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
37 t.integer "contest_id"
37 t.integer "contest_id"
38 t.integer "problem_id"
38 t.integer "problem_id"
39 end
39 end
40
40
41 - create_table "contests_users", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
41 + create_table "contests_users", id: false, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
42 t.integer "contest_id"
42 t.integer "contest_id"
43 t.integer "user_id"
43 t.integer "user_id"
44 end
44 end
45
45
46 - create_table "countries", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
46 + create_table "countries", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
47 t.string "name"
47 t.string "name"
48 t.datetime "created_at", null: false
48 t.datetime "created_at", null: false
49 t.datetime "updated_at", null: false
49 t.datetime "updated_at", null: false
50 end
50 end
51
51
52 - create_table "descriptions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
52 + create_table "descriptions", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
53 - t.text "body", limit: 16777215
53 + t.text "body"
54 t.boolean "markdowned"
54 t.boolean "markdowned"
55 t.datetime "created_at", null: false
55 t.datetime "created_at", null: false
56 t.datetime "updated_at", null: false
56 t.datetime "updated_at", null: false
57 end
57 end
58
58
59 - create_table "grader_configurations", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
59 + create_table "grader_configurations", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
60 t.string "key"
60 t.string "key"
61 t.string "value_type"
61 t.string "value_type"
62 t.string "value"
62 t.string "value"
63 t.datetime "created_at", null: false
63 t.datetime "created_at", null: false
64 t.datetime "updated_at", null: false
64 t.datetime "updated_at", null: false
65 - t.text "description", limit: 16777215
65 + t.text "description"
66 end
66 end
67
67
68 - create_table "grader_processes", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
68 + create_table "grader_processes", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
69 t.string "host"
69 t.string "host"
70 t.integer "pid"
70 t.integer "pid"
71 t.string "mode"
71 t.string "mode"
72 t.boolean "active"
72 t.boolean "active"
73 t.datetime "created_at", null: false
73 t.datetime "created_at", null: false
74 t.datetime "updated_at", null: false
74 t.datetime "updated_at", null: false
75 t.integer "task_id"
75 t.integer "task_id"
76 t.string "task_type"
76 t.string "task_type"
77 t.boolean "terminated"
77 t.boolean "terminated"
78 t.index ["host", "pid"], name: "index_grader_processes_on_ip_and_pid"
78 t.index ["host", "pid"], name: "index_grader_processes_on_ip_and_pid"
79 end
79 end
80
80
81 - create_table "groups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
81 + create_table "groups", id: :integer, charset: "latin1", force: :cascade do |t|
82 t.string "name"
82 t.string "name"
83 t.string "description"
83 t.string "description"
84 t.boolean "enabled", default: true
84 t.boolean "enabled", default: true
85 end
85 end
86
86
87 - create_table "groups_problems", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
87 + create_table "groups_problems", id: false, charset: "latin1", force: :cascade do |t|
88 t.integer "problem_id", null: false
88 t.integer "problem_id", null: false
89 t.integer "group_id", null: false
89 t.integer "group_id", null: false
90 t.index ["group_id", "problem_id"], name: "index_groups_problems_on_group_id_and_problem_id"
90 t.index ["group_id", "problem_id"], name: "index_groups_problems_on_group_id_and_problem_id"
91 end
91 end
92
92
93 - create_table "groups_users", options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
93 + create_table "groups_users", charset: "latin1", force: :cascade do |t|
94 t.integer "group_id", null: false
94 t.integer "group_id", null: false
95 t.integer "user_id", null: false
95 t.integer "user_id", null: false
96 t.index ["user_id", "group_id"], name: "index_groups_users_on_user_id_and_group_id"
96 t.index ["user_id", "group_id"], name: "index_groups_users_on_user_id_and_group_id"
97 end
97 end
98
98
99 - create_table "heart_beats", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
99 + create_table "heart_beats", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
100 t.integer "user_id"
100 t.integer "user_id"
101 t.string "ip_address"
101 t.string "ip_address"
102 t.datetime "created_at", null: false
102 t.datetime "created_at", null: false
103 t.datetime "updated_at", null: false
103 t.datetime "updated_at", null: false
104 t.string "status"
104 t.string "status"
105 t.index ["updated_at"], name: "index_heart_beats_on_updated_at"
105 t.index ["updated_at"], name: "index_heart_beats_on_updated_at"
106 end
106 end
107
107
108 - create_table "languages", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
108 + create_table "languages", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
109 t.string "name", limit: 10
109 t.string "name", limit: 10
110 t.string "pretty_name"
110 t.string "pretty_name"
111 t.string "ext", limit: 10
111 t.string "ext", limit: 10
112 t.string "common_ext"
112 t.string "common_ext"
113 end
113 end
114
114
115 - create_table "logins", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
115 + create_table "logins", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
116 t.integer "user_id"
116 t.integer "user_id"
117 t.string "ip_address"
117 t.string "ip_address"
118 t.datetime "created_at", null: false
118 t.datetime "created_at", null: false
119 t.datetime "updated_at", null: false
119 t.datetime "updated_at", null: false
120 t.index ["user_id"], name: "index_logins_on_user_id"
120 t.index ["user_id"], name: "index_logins_on_user_id"
121 end
121 end
122
122
123 - create_table "messages", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
123 + create_table "messages", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
124 t.integer "sender_id"
124 t.integer "sender_id"
125 t.integer "receiver_id"
125 t.integer "receiver_id"
126 t.integer "replying_message_id"
126 t.integer "replying_message_id"
127 - t.text "body", limit: 16777215
127 + t.text "body"
128 t.boolean "replied"
128 t.boolean "replied"
129 t.datetime "created_at", null: false
129 t.datetime "created_at", null: false
130 t.datetime "updated_at", null: false
130 t.datetime "updated_at", null: false
131 end
131 end
132
132
133 - create_table "problems", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
133 + create_table "problems", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
134 - t.string "name", limit: 100
134 + t.string "name", limit: 30
135 t.string "full_name"
135 t.string "full_name"
136 t.integer "full_score"
136 t.integer "full_score"
137 t.date "date_added"
137 t.date "date_added"
138 t.boolean "available"
138 t.boolean "available"
139 t.string "url"
139 t.string "url"
140 t.integer "description_id"
140 t.integer "description_id"
141 t.boolean "test_allowed"
141 t.boolean "test_allowed"
142 t.boolean "output_only"
142 t.boolean "output_only"
143 t.string "description_filename"
143 t.string "description_filename"
144 t.boolean "view_testcase"
144 t.boolean "view_testcase"
145 + t.integer "difficulty"
145 end
146 end
146
147
147 - create_table "problems_tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
148 + create_table "problems_tags", id: :integer, charset: "latin1", force: :cascade do |t|
148 t.integer "problem_id"
149 t.integer "problem_id"
149 t.integer "tag_id"
150 t.integer "tag_id"
150 t.index ["problem_id", "tag_id"], name: "index_problems_tags_on_problem_id_and_tag_id", unique: true
151 t.index ["problem_id", "tag_id"], name: "index_problems_tags_on_problem_id_and_tag_id", unique: true
151 t.index ["problem_id"], name: "index_problems_tags_on_problem_id"
152 t.index ["problem_id"], name: "index_problems_tags_on_problem_id"
152 t.index ["tag_id"], name: "index_problems_tags_on_tag_id"
153 t.index ["tag_id"], name: "index_problems_tags_on_tag_id"
153 end
154 end
154
155
155 - create_table "rights", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
156 + create_table "rights", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
156 t.string "name"
157 t.string "name"
157 t.string "controller"
158 t.string "controller"
158 t.string "action"
159 t.string "action"
159 end
160 end
160
161
161 - create_table "rights_roles", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
162 + create_table "rights_roles", id: false, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
162 t.integer "right_id"
163 t.integer "right_id"
163 t.integer "role_id"
164 t.integer "role_id"
164 t.index ["role_id"], name: "index_rights_roles_on_role_id"
165 t.index ["role_id"], name: "index_rights_roles_on_role_id"
165 end
166 end
166
167
167 - create_table "roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
168 + create_table "roles", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
168 t.string "name"
169 t.string "name"
169 end
170 end
170
171
171 - create_table "roles_users", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
172 + create_table "roles_users", id: false, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
172 t.integer "role_id"
173 t.integer "role_id"
173 t.integer "user_id"
174 t.integer "user_id"
174 t.index ["user_id"], name: "index_roles_users_on_user_id"
175 t.index ["user_id"], name: "index_roles_users_on_user_id"
175 end
176 end
176
177
177 - create_table "sessions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
178 + create_table "sessions", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
178 t.string "session_id"
179 t.string "session_id"
179 - t.text "data", limit: 16777215
180 + t.text "data"
180 t.datetime "updated_at"
181 t.datetime "updated_at"
181 t.index ["session_id"], name: "index_sessions_on_session_id"
182 t.index ["session_id"], name: "index_sessions_on_session_id"
182 t.index ["updated_at"], name: "index_sessions_on_updated_at"
183 t.index ["updated_at"], name: "index_sessions_on_updated_at"
183 end
184 end
184
185
185 - create_table "sites", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
186 + create_table "sites", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
186 t.string "name"
187 t.string "name"
187 t.boolean "started"
188 t.boolean "started"
188 t.datetime "start_time"
189 t.datetime "start_time"
189 t.datetime "created_at", null: false
190 t.datetime "created_at", null: false
190 t.datetime "updated_at", null: false
191 t.datetime "updated_at", null: false
191 t.integer "country_id"
192 t.integer "country_id"
192 t.string "password"
193 t.string "password"
193 end
194 end
194
195
195 - create_table "submission_view_logs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
196 + create_table "submission_view_logs", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
196 t.integer "user_id"
197 t.integer "user_id"
197 t.integer "submission_id"
198 t.integer "submission_id"
198 t.datetime "created_at", null: false
199 t.datetime "created_at", null: false
199 t.datetime "updated_at", null: false
200 t.datetime "updated_at", null: false
200 end
201 end
201
202
202 - create_table "submissions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
203 + create_table "submissions", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
203 t.integer "user_id"
204 t.integer "user_id"
204 t.integer "problem_id"
205 t.integer "problem_id"
205 t.integer "language_id"
206 t.integer "language_id"
206 - t.text "source", limit: 16777215
207 + t.text "source", size: :medium
207 t.binary "binary"
208 t.binary "binary"
208 t.datetime "submitted_at"
209 t.datetime "submitted_at"
209 t.datetime "compiled_at"
210 t.datetime "compiled_at"
210 - t.text "compiler_message", limit: 16777215
211 + t.text "compiler_message"
211 t.datetime "graded_at"
212 t.datetime "graded_at"
212 t.integer "points"
213 t.integer "points"
213 - t.text "grader_comment", limit: 16777215
214 + t.text "grader_comment"
214 t.integer "number"
215 t.integer "number"
215 t.string "source_filename"
216 t.string "source_filename"
216 t.float "max_runtime"
217 t.float "max_runtime"
217 t.integer "peak_memory"
218 t.integer "peak_memory"
218 t.integer "effective_code_length"
219 t.integer "effective_code_length"
219 t.string "ip_address"
220 t.string "ip_address"
221 + t.integer "tag", default: 0
220 t.index ["submitted_at"], name: "index_submissions_on_submitted_at"
222 t.index ["submitted_at"], name: "index_submissions_on_submitted_at"
221 t.index ["user_id", "problem_id", "number"], name: "index_submissions_on_user_id_and_problem_id_and_number", unique: true
223 t.index ["user_id", "problem_id", "number"], name: "index_submissions_on_user_id_and_problem_id_and_number", unique: true
222 t.index ["user_id", "problem_id"], name: "index_submissions_on_user_id_and_problem_id"
224 t.index ["user_id", "problem_id"], name: "index_submissions_on_user_id_and_problem_id"
223 end
225 end
224
226
225 - create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
227 + create_table "tags", id: :integer, charset: "latin1", force: :cascade do |t|
226 t.string "name", null: false
228 t.string "name", null: false
227 t.text "description"
229 t.text "description"
228 t.boolean "public"
230 t.boolean "public"
229 t.datetime "created_at", null: false
231 t.datetime "created_at", null: false
230 t.datetime "updated_at", null: false
232 t.datetime "updated_at", null: false
231 end
233 end
232
234
233 - create_table "tasks", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
235 + create_table "tasks", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
234 t.integer "submission_id"
236 t.integer "submission_id"
235 t.datetime "created_at"
237 t.datetime "created_at"
236 t.integer "status"
238 t.integer "status"
237 t.datetime "updated_at"
239 t.datetime "updated_at"
238 t.index ["status"], name: "index_tasks_on_status"
240 t.index ["status"], name: "index_tasks_on_status"
239 t.index ["submission_id"], name: "index_tasks_on_submission_id"
241 t.index ["submission_id"], name: "index_tasks_on_submission_id"
240 end
242 end
241
243
242 - create_table "test_pairs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
244 + create_table "test_pairs", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
243 t.integer "problem_id"
245 t.integer "problem_id"
244 - t.text "input", limit: 4294967295
246 + t.text "input", size: :medium
245 - t.text "solution", limit: 4294967295
247 + t.text "solution", size: :medium
246 t.datetime "created_at", null: false
248 t.datetime "created_at", null: false
247 t.datetime "updated_at", null: false
249 t.datetime "updated_at", null: false
248 end
250 end
249
251
250 - create_table "test_requests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
252 + create_table "test_requests", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
251 t.integer "user_id"
253 t.integer "user_id"
252 t.integer "problem_id"
254 t.integer "problem_id"
253 t.integer "submission_id"
255 t.integer "submission_id"
254 t.string "input_file_name"
256 t.string "input_file_name"
255 t.string "output_file_name"
257 t.string "output_file_name"
256 t.string "running_stat"
258 t.string "running_stat"
257 t.integer "status"
259 t.integer "status"
258 t.datetime "updated_at", null: false
260 t.datetime "updated_at", null: false
259 t.datetime "submitted_at"
261 t.datetime "submitted_at"
260 t.datetime "compiled_at"
262 t.datetime "compiled_at"
261 - t.text "compiler_message", limit: 16777215
263 + t.text "compiler_message"
262 t.datetime "graded_at"
264 t.datetime "graded_at"
263 t.string "grader_comment"
265 t.string "grader_comment"
264 t.datetime "created_at", null: false
266 t.datetime "created_at", null: false
265 t.float "running_time"
267 t.float "running_time"
266 t.string "exit_status"
268 t.string "exit_status"
267 t.integer "memory_usage"
269 t.integer "memory_usage"
268 t.index ["user_id", "problem_id"], name: "index_test_requests_on_user_id_and_problem_id"
270 t.index ["user_id", "problem_id"], name: "index_test_requests_on_user_id_and_problem_id"
269 end
271 end
270
272
271 - create_table "testcases", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
273 + create_table "testcases", id: :integer, charset: "latin1", force: :cascade do |t|
272 t.integer "problem_id"
274 t.integer "problem_id"
273 t.integer "num"
275 t.integer "num"
274 t.integer "group"
276 t.integer "group"
275 t.integer "score"
277 t.integer "score"
276 - t.text "input", limit: 4294967295
278 + t.text "input", size: :long
277 - t.text "sol", limit: 4294967295
279 + t.text "sol", size: :long
278 - t.datetime "created_at", null: false
280 + t.datetime "created_at"
279 - t.datetime "updated_at", null: false
281 + t.datetime "updated_at"
280 t.index ["problem_id"], name: "index_testcases_on_problem_id"
282 t.index ["problem_id"], name: "index_testcases_on_problem_id"
281 end
283 end
282
284
283 - create_table "user_contest_stats", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
285 + create_table "user_contest_stats", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
284 t.integer "user_id"
286 t.integer "user_id"
285 t.datetime "started_at"
287 t.datetime "started_at"
286 t.datetime "created_at", null: false
288 t.datetime "created_at", null: false
287 t.datetime "updated_at", null: false
289 t.datetime "updated_at", null: false
288 t.boolean "forced_logout"
290 t.boolean "forced_logout"
289 end
291 end
290
292
291 - create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
293 + create_table "users", id: :integer, charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t|
292 t.string "login", limit: 50
294 t.string "login", limit: 50
293 t.string "full_name"
295 t.string "full_name"
294 t.string "hashed_password"
296 t.string "hashed_password"
295 t.string "salt", limit: 5
297 t.string "salt", limit: 5
296 t.string "alias"
298 t.string "alias"
297 t.string "email"
299 t.string "email"
298 t.integer "site_id"
300 t.integer "site_id"
299 t.integer "country_id"
301 t.integer "country_id"
300 t.boolean "activated", default: false
302 t.boolean "activated", default: false
301 t.datetime "created_at"
303 t.datetime "created_at"
302 t.datetime "updated_at"
304 t.datetime "updated_at"
303 - t.string "section"
304 t.boolean "enabled", default: true
305 t.boolean "enabled", default: true
305 t.string "remark"
306 t.string "remark"
306 t.string "last_ip"
307 t.string "last_ip"
308 + t.string "section"
307 t.integer "default_language"
309 t.integer "default_language"
308 t.index ["login"], name: "index_users_on_login", unique: true
310 t.index ["login"], name: "index_users_on_login", unique: true
309 end
311 end
310
312
311 add_foreign_key "problems_tags", "problems"
313 add_foreign_key "problems_tags", "problems"
312 add_foreign_key "problems_tags", "tags"
314 add_foreign_key "problems_tags", "tags"
313 end
315 end
You need to be logged in to leave comments. Login now