Description:
Merged online-registration branch changes r297:303 into the trunk git-svn-id: http://theory.cpe.ku.ac.th/grader/web/trunk@303 6386c4cd-e34a-4fa8-8920-d93eb39b512e
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r158:13c9210ae5ca - - 16 files changed: 318 inserted, 29 deleted

@@ -0,0 +1,15
1 + - if @result == :successful
2 + %h1 User activated
3 + Your account has been activated.
4 + %br/
5 + Please go ahead to
6 + = link_to 'login.', :controller => 'main', :action => 'login'
7 + - else
8 + %h1 Activation failed
9 + - if @result == :email_used
10 + A user with this E-mail exists.
11 + - else
12 + Your activation code is invalid. Please check again.
13 + %br/
14 + Get back to
15 + = link_to 'home.', :controller => 'main', :action => 'login'
@@ -0,0 +1,9
1 + %h1 Errors in sending registration confirmation
2 +
3 + .errorExplanation
4 + %h2
5 + Your user account has been created, but the system cannot send you the
6 + confirmation e-mail.
7 +
8 + Maybe there's a problem in the configuration. Please report the
9 + admin. Thank you!
@@ -0,0 +1,18
1 + class AddEmailingInfoOnConfigOption < ActiveRecord::Migration
2 + def self.up
3 + # If Configuration['system.online_registration'] is true, the
4 + # system allows online registration, and will use these
5 + # information for sending confirmation emails.
6 + Configuration.create(:key => 'system.online_registration.smtp',
7 + :value_type => 'string',
8 + :value => 'smtp.somehost.com')
9 + Configuration.create(:key => 'system.online_registration.from',
10 + :value_type => 'string',
11 + :value => 'your.email@address')
12 + end
13 +
14 + def self.down
15 + Configuration.find_by_key("system.online_registration.smtp").destroy
16 + Configuration.find_by_key("system.online_registration.from").destroy
17 + end
18 + end
@@ -0,0 +1,9
1 + class AddTimestampsToUsers < ActiveRecord::Migration
2 + def self.up
3 + add_timestamps :users
4 + end
5 +
6 + def self.down
7 + remove_timestamps :users
8 + end
9 + end
@@ -0,0 +1,95
1 +
2 + require File.dirname(__FILE__) + '/../spec_helper'
3 +
4 + describe UsersController, "when a new user registers" do
5 +
6 + before(:each) do
7 + # create john
8 +
9 + @john_info = {:login => 'john',
10 + :full_name => 'John John',
11 + :email => 'john@space.com'}
12 + @john = User.new(@john_info)
13 +
14 + @john_activation_key = "123456"
15 +
16 + @john.should_receive(:activation_key).
17 + any_number_of_times.
18 + and_return(@john_activation_key)
19 +
20 + get :new
21 + response.should render_template('users/new')
22 + end
23 +
24 + it "should show the new form again when user information is invalid" do
25 + User.should_receive(:new).with(any_args()).and_return(@john)
26 + @john.should_receive(:activated=).with(false)
27 + @john.should_receive(:valid?).and_return(false)
28 + @john.should_not_receive(:save)
29 +
30 + post :register, :login => @john_info[:login],
31 + :full_name => @john_info[:full_name],
32 + :email => @john_info[:email]
33 +
34 + response.should render_template('users/new')
35 + end
36 +
37 + it "should create unactivated user and send e-mail with activation key" do
38 + User.should_receive(:new).with(any_args()).and_return(@john)
39 + @john.should_receive(:activated=).with(false)
40 + @john.should_receive(:valid?).and_return(true)
41 + @john.should_receive(:save).and_return(true)
42 +
43 + smtp_mock = mock("smtp")
44 + smtp_mock.should_receive(:send_message) do |msg,fr,to|
45 + to.should == [@john_info[:email]]
46 + msg.index(@john_activation_key).should_not be_nil
47 + end
48 +
49 + Net::SMTP.should_receive(:start).
50 + with(any_args()).
51 + and_yield(smtp_mock)
52 +
53 + post :register, :login => @john_info[:login],
54 + :full_name => @john_info[:full_name],
55 + :email => @john_info[:email]
56 +
57 + response.should render_template('users/new_splash')
58 + end
59 +
60 + it "should create unactivated user and return error page when e-mail sending error" do
61 + User.should_receive(:new).with(any_args()).and_return(@john)
62 + @john.should_receive(:activated=).with(false)
63 + @john.should_receive(:valid?).and_return(true)
64 + @john.should_receive(:save).and_return(true)
65 +
66 + smtp_mock = mock("smtp")
67 + smtp_mock.should_receive(:send_message).
68 + and_throw(:error)
69 +
70 + Net::SMTP.should_receive(:start).
71 + with(any_args()).
72 + and_yield(smtp_mock)
73 +
74 + post :register, :login => @john_info[:login],
75 + :full_name => @john_info[:full_name],
76 + :email => @john_info[:email]
77 +
78 + response.should render_template('users/email_error')
79 + end
80 +
81 + it "should activate user with valid activation key" do
82 + login = @john_info[:login]
83 + User.should_receive(:find_by_login).
84 + with(login).
85 + and_return(@john)
86 + @john.should_receive(:valid?).and_return(true)
87 + @john.should_receive(:activated=).with(true)
88 + @john.should_receive(:save).and_return(true)
89 +
90 + get :confirm, :login => login, :activation => @john_activation_key
91 +
92 + response.should render_template('users/confirm')
93 + end
94 +
95 + end
@@ -1,56 +1,116
1 - require 'pony'
1 + require 'tmail'
2 + require 'net/smtp'
2 3
3 4 class UsersController < ApplicationController
4 5
5 - before_filter :authenticate, :except => [:new, :register]
6 + before_filter :authenticate, :except => [:new, :register, :confirm]
6 7
7 8 verify :method => :post, :only => [:chg_passwd],
8 9 :redirect_to => { :action => :index }
9 10
10 11 in_place_edit_for :user, :alias_for_editing
11 12 in_place_edit_for :user, :email_for_editing
12 13
13 14 def index
14 15 if !Configuration['system.user_setting_enabled']
15 16 redirect_to :controller => 'main', :action => 'list'
16 17 else
17 18 @user = User.find(session[:user_id])
18 19 end
19 20 end
20 21
21 22 def chg_passwd
22 23 user = User.find(session[:user_id])
23 24 user.password = params[:passwd]
24 25 user.password_confirmation = params[:passwd_verify]
25 26 if user.save
26 27 flash[:notice] = 'password changed'
27 28 else
28 29 flash[:notice] = 'Error: password changing failed'
29 30 end
30 31 redirect_to :action => 'index'
31 32 end
32 33
33 34 def new
34 35 @user = User.new
35 36 render :action => 'new', :layout => 'empty'
36 37 end
37 38
38 39 def register
39 40 @user = User.new(params[:user])
40 41 @user.password_confirmation = @user.password = User.random_password
41 42 @user.activated = false
42 43 if (@user.valid?) and (@user.save)
43 - send_confirmation_email(@user)
44 - render :action => 'new_splash', :layout => 'empty'
44 + if send_confirmation_email(@user)
45 + render :action => 'new_splash', :layout => 'empty'
46 + else
47 + render :action => 'email_error', :layout => 'empty'
48 + end
45 49 else
46 50 @user.errors.add_to_base("Email cannot be blank") if @user.email==''
47 51 render :action => 'new', :layout => 'empty'
48 52 end
49 53 end
50 54
55 + def confirm
56 + login = params[:login]
57 + key = params[:activation]
58 + user = User.find_by_login(login)
59 + if (user) and (user.verify_activation_key(key))
60 + if user.valid? # check uniquenss of email
61 + user.activated = true
62 + user.save
63 + @result = :successful
64 + else
65 + @result = :email_used
66 + end
67 + else
68 + @result = :failed
69 + end
70 + render :action => 'confirm', :layout => 'empty'
71 + end
72 +
51 73 protected
52 74
53 75 def send_confirmation_email(user)
76 + contest_name = Configuration['contest.name']
77 + activation_url = url_for(:action => 'confirm',
78 + :login => user.login,
79 + :activation => user.activation_key)
80 + home_url = url_for(:controller => 'main', :action => 'index')
81 + mail = TMail::Mail.new
82 + mail.to = user.email
83 + mail.from = Configuration['system.online_registration.from']
84 + mail.subject = "[#{contest_name}] Confirmation"
85 + mail.body = <<-EOF
86 + Hello #{user.full_name},
87 +
88 + You have registered for #{contest_name} (#{home_url}).
89 +
90 + Your login is: #{user.login}
91 + Your password is: #{user.password}
92 +
93 + Please follow the link:
94 + #{activation_url}
95 + to activate your user account.
96 +
97 + If you did not register, please ignore this e-mail.
98 +
99 + Thanks!
100 + EOF
101 +
102 + smtp_server = Configuration['system.online_registration.smtp']
103 +
104 + begin
105 + Net::SMTP.start(smtp_server) do |smtp|
106 + smtp.send_message(mail.to_s, mail.from, mail.to)
107 + end
108 + result = true
109 + rescue
110 + result = false
111 + end
112 +
113 + return result
54 114 end
55 115
56 116 end
@@ -1,119 +1,123
1 1 require 'yaml'
2 2
3 3 #
4 4 # This class also contains various login of the system.
5 5 #
6 6 class Configuration < ActiveRecord::Base
7 7
8 8 SYSTEM_MODE_CONF_KEY = 'system.mode'
9 9
10 10 # set @@cache = true to only reload once.
11 11 @@cache = false
12 12
13 13 @@configurations = nil
14 14 @@task_grading_info = nil
15 15
16 16 def self.get(key)
17 17 if @@cache
18 18 if @@configurations == nil
19 19 self.read_config
20 20 end
21 21 return @@configurations[key]
22 22 else
23 23 return Configuration.read_one_key(key)
24 24 end
25 25 end
26 26
27 27 def self.[](key)
28 28 self.get(key)
29 29 end
30 30
31 31 def self.reload
32 32 self.read_config
33 33 end
34 34
35 35 def self.clear
36 36 @@configurations = nil
37 37 end
38 38
39 + def self.enable_caching
40 + @@cache = true
41 + end
42 +
39 43 #
40 44 # View decision
41 45 #
42 46 def self.show_submitbox_to?(user)
43 47 mode = get(SYSTEM_MODE_CONF_KEY)
44 48 return false if mode=='analysis'
45 49 if (mode=='contest')
46 50 return false if (user.site!=nil) and
47 51 ((user.site.started!=true) or (user.site.finished?))
48 52 end
49 53 return true
50 54 end
51 55
52 56 def self.show_tasks_to?(user)
53 57 mode = get(SYSTEM_MODE_CONF_KEY)
54 58 if (mode=='contest')
55 59 return false if (user.site!=nil) and (user.site.started!=true)
56 60 end
57 61 return true
58 62 end
59 63
60 64 def self.show_grading_result
61 65 return (get(SYSTEM_MODE_CONF_KEY)=='analysis')
62 66 end
63 67
64 68 def self.allow_test_request(user)
65 69 mode = get(SYSTEM_MODE_CONF_KEY)
66 70 if (mode=='contest')
67 71 return false if (user.site!=nil) and ((user.site.started!=true) or (user.site.time_left < 30.minutes))
68 72 end
69 73 return false if mode=='analysis'
70 74 return true
71 75 end
72 76
73 77 def self.task_grading_info
74 78 if @@task_grading_info==nil
75 79 read_grading_info
76 80 end
77 81 return @@task_grading_info
78 82 end
79 83
80 84 protected
81 85
82 86 def self.convert_type(val,type)
83 87 case type
84 88 when 'string'
85 89 return val
86 90
87 91 when 'integer'
88 92 return val.to_i
89 93
90 94 when 'boolean'
91 95 return (val=='true')
92 96 end
93 97 end
94 98
95 99 def self.read_config
96 100 @@configurations = {}
97 101 Configuration.find(:all).each do |conf|
98 102 key = conf.key
99 103 val = conf.value
100 104 @@configurations[key] = Configuration.convert_type(val,conf.value_type)
101 105 end
102 106 end
103 107
104 108 def self.read_one_key(key)
105 109 conf = Configuration.find_by_key(key)
106 110 if conf
107 111 return Configuration.convert_type(conf.value,conf.value_type)
108 112 else
109 113 return nil
110 114 end
111 115 end
112 116
113 117 def self.read_grading_info
114 118 f = File.open(TASK_GRADING_INFO_FILENAME)
115 119 @@task_grading_info = YAML.load(f)
116 120 f.close
117 121 end
118 122
119 123 end
@@ -1,124 +1,138
1 1 require 'digest/sha1'
2 2
3 3 class User < ActiveRecord::Base
4 4
5 5 has_and_belongs_to_many :roles
6 6
7 7 has_many :test_requests, :order => "submitted_at DESC"
8 8
9 9 has_many :messages,
10 10 :class_name => "Message",
11 11 :foreign_key => "sender_id",
12 12 :order => 'created_at DESC'
13 13
14 14 has_many :replied_messages,
15 15 :class_name => "Message",
16 16 :foreign_key => "receiver_id",
17 17 :order => 'created_at DESC'
18 18
19 19 belongs_to :site
20 20 belongs_to :country
21 21
22 - named_scope :activated, :conditions => {:activated => true}
22 + named_scope :activated_users, :conditions => {:activated => true}
23 23
24 24 validates_presence_of :login
25 25 validates_uniqueness_of :login
26 26 validates_format_of :login, :with => /^[\_a-z0-9]+$/
27 27 validates_length_of :login, :within => 3..10
28 28
29 29 validates_presence_of :full_name
30 30 validates_length_of :full_name, :minimum => 1
31 31
32 32 validates_presence_of :password, :if => :password_required?
33 33 validates_length_of :password, :within => 4..20, :if => :password_required?
34 34 validates_confirmation_of :password, :if => :password_required?
35 35
36 36 validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :allow_blank => true
37 37
38 38 validate :uniqueness_of_email_from_activated_users
39 + validate :enough_time_interval_between_same_email_registrations
39 40
40 41 attr_accessor :password
41 42
42 43 before_save :encrypt_new_password
43 44
44 45 def self.authenticate(login, password)
45 46 user = find_by_login(login)
46 47 return user if user && user.authenticated?(password)
47 48 end
48 49
49 50 def authenticated?(password)
50 51 if self.activated
51 52 hashed_password == User.encrypt(password,self.salt)
52 53 else
53 54 false
54 55 end
55 56 end
56 57
57 58 def admin?
58 59 self.roles.detect {|r| r.name == 'admin' }
59 60 end
60 61
61 62 def email_for_editing
62 63 if self.email==nil
63 64 "(unknown)"
64 65 elsif self.email==''
65 66 "(blank)"
66 67 else
67 68 self.email
68 69 end
69 70 end
70 71
71 72 def email_for_editing=(e)
72 73 self.email=e
73 74 end
74 75
75 76 def alias_for_editing
76 77 if self.alias==nil
77 78 "(unknown)"
78 79 elsif self.alias==''
79 80 "(blank)"
80 81 else
81 82 self.alias
82 83 end
83 84 end
84 85
85 86 def alias_for_editing=(e)
86 87 self.alias=e
87 88 end
88 89
89 90 def activation_key
91 + if self.hashed_password==nil
92 + encrypt_new_password
93 + end
90 94 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
91 95 end
92 96
93 97 def verify_activation_key(key)
94 98 key == activation_key
95 99 end
96 100
97 101 def self.random_password(length=5)
98 102 chars = 'abcdefghjkmnopqrstuvwxyz'
99 103 password = ''
100 104 length.times { password << chars[rand(chars.length - 1)] }
101 105 password
102 106 end
103 107
104 108 protected
105 109 def encrypt_new_password
106 110 return if password.blank?
107 111 self.salt = (10+rand(90)).to_s
108 112 self.hashed_password = User.encrypt(self.password,self.salt)
109 113 end
110 114
111 115 def password_required?
112 116 self.hashed_password.blank? || !self.password.blank?
113 117 end
114 118
115 119 def self.encrypt(string,salt)
116 120 Digest::SHA1.hexdigest(salt + string)
117 121 end
118 122
119 123 def uniqueness_of_email_from_activated_users
120 - if User.activated.find_by_email(self.email)!=nil
124 + user = User.activated_users.find_by_email(self.email)
125 + if user and (user.login != self.login)
121 126 self.errors.add_to_base("Email has already been taken")
122 127 end
123 128 end
129 +
130 + def enough_time_interval_between_same_email_registrations
131 + open_user = User.find_by_email(self.email,
132 + :order => 'created_at DESC')
133 + if open_user and open_user.created_at and
134 + (open_user.created_at > Time.now.gmtime - 5.minutes)
135 + self.errors.add_to_base("There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
136 + end
137 + end
124 138 end
@@ -1,74 +1,77
1 1 # Be sure to restart your web server when you modify this file.
2 2
3 3 # Uncomment below to force Rails into production mode when
4 4 # you don't control web/app server and can't set it the proper way
5 5 # ENV['RAILS_ENV'] ||= 'production'
6 6
7 7 # Specifies gem version of Rails to use when vendor/rails is not present
8 8 RAILS_GEM_VERSION = '2.1.1' unless defined? RAILS_GEM_VERSION
9 9
10 10 # Bootstrap the Rails environment, frameworks, and default configuration
11 11 require File.join(File.dirname(__FILE__), 'boot')
12 12
13 13 Rails::Initializer.run do |config|
14 14 # Settings in config/environments/* take precedence over those specified here
15 15
16 16 # Skip frameworks you're not going to use (only works if using vendor/rails)
17 17 # config.frameworks -= [ :action_web_service, :action_mailer ]
18 18
19 19 # Only load the plugins named here, by default all plugins in vendor/plugins are loaded
20 20 # config.plugins = %W( exception_notification ssl_requirement )
21 21
22 22 # Add additional load paths for your own custom dirs
23 23 # config.load_paths += %W( #{RAILS_ROOT}/extras )
24 24
25 25 # Force all environments to use the same logger level
26 26 # (by default production uses :info, the others :debug)
27 27 # config.log_level = :debug
28 28
29 29 # Use the database for sessions instead of the file system
30 30 # (create the session table with 'rake db:sessions:create')
31 31 config.action_controller.session_store = :active_record_store
32 32
33 33 # Use SQL instead of Active Record's schema dumper when creating the test database.
34 34 # This is necessary if your schema can't be completely dumped by the schema dumper,
35 35 # like if you have constraints or database-specific column types
36 36 # config.active_record.schema_format = :sql
37 37
38 38 # Activate observers that should always be running
39 39 # config.active_record.observers = :cacher, :garbage_collector
40 40
41 41 # Make Active Record use UTC-base instead of local time
42 42 config.active_record.default_timezone = :utc
43 43
44 44 # See Rails::Configuration for more options
45 45
46 46 # -------------
47 47 # Required gems
48 48 # -------------
49 49
50 50 # This is for rspec
51 51 config.gem "rspec-rails", :lib => "spec"
52 52 config.gem "haml"
53 - config.gem "pony"
53 + config.gem "tmail"
54 54 #config.gem "BlueCloth", :lig => "bluecloth"
55 55 end
56 56
57 57 # Add new inflection rules using the following format
58 58 # (all these examples are active by default):
59 59 # Inflector.inflections do |inflect|
60 60 # inflect.plural /^(ox)$/i, '\1en'
61 61 # inflect.singular /^(ox)en/i, '\1'
62 62 # inflect.irregular 'person', 'people'
63 63 # inflect.uncountable %w( fish sheep )
64 64 # end
65 65
66 66 # Add new mime types for use in respond_to blocks:
67 67 # Mime::Type.register "text/richtext", :rtf
68 68 # Mime::Type.register "application/x-mobile", :mobile
69 69
70 70 # Include your application configuration below
71 71
72 72 # These are where inputs and outputs of test requests are stored
73 73 TEST_REQUEST_INPUT_FILE_DIR = RAILS_ROOT + '/data/test_request/input'
74 74 TEST_REQUEST_OUTPUT_FILE_DIR = RAILS_ROOT + '/data/test_request/output'
75 +
76 + # Uncomment so that configuration is read only once when the server is loaded
77 + # Configuration.enable_caching
@@ -1,190 +1,192
1 1 # This file is auto-generated from the current state of the database. Instead of editing this file,
2 2 # please use the migrations feature of Active Record to incrementally modify your database, and
3 3 # then regenerate this schema definition.
4 4 #
5 5 # Note that this schema.rb definition is the authoritative source for your database schema. If you need
6 6 # to create the application database on another system, you should be using db:schema:load, not running
7 7 # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
8 8 # you'll amass, the slower it'll run and the greater likelihood for issues).
9 9 #
10 10 # It's strongly recommended to check this file into your version control system.
11 11
12 - ActiveRecord::Schema.define(:version => 20081204122651) do
12 + ActiveRecord::Schema.define(:version => 20081210021333) do
13 13
14 14 create_table "announcements", :force => true do |t|
15 15 t.string "author"
16 16 t.text "body"
17 17 t.boolean "published"
18 18 t.datetime "created_at"
19 19 t.datetime "updated_at"
20 20 t.boolean "frontpage", :default => false
21 21 end
22 22
23 23 create_table "configurations", :force => true do |t|
24 24 t.string "key"
25 25 t.string "value_type"
26 26 t.string "value"
27 27 t.datetime "created_at"
28 28 t.datetime "updated_at"
29 29 end
30 30
31 31 create_table "countries", :force => true do |t|
32 32 t.string "name"
33 33 t.datetime "created_at"
34 34 t.datetime "updated_at"
35 35 end
36 36
37 37 create_table "descriptions", :force => true do |t|
38 38 t.text "body"
39 39 t.boolean "markdowned"
40 40 t.datetime "created_at"
41 41 t.datetime "updated_at"
42 42 end
43 43
44 44 create_table "grader_processes", :force => true do |t|
45 45 t.string "host", :limit => 20
46 46 t.integer "pid"
47 47 t.string "mode"
48 48 t.boolean "active"
49 49 t.datetime "created_at"
50 50 t.datetime "updated_at"
51 51 t.integer "task_id"
52 52 t.string "task_type"
53 53 end
54 54
55 55 add_index "grader_processes", ["host", "pid"], :name => "index_grader_processes_on_ip_and_pid"
56 56
57 57 create_table "languages", :force => true do |t|
58 58 t.string "name", :limit => 10
59 59 t.string "pretty_name"
60 60 t.string "ext", :limit => 10
61 61 end
62 62
63 63 create_table "messages", :force => true do |t|
64 64 t.integer "sender_id"
65 65 t.integer "receiver_id"
66 66 t.integer "replying_message_id"
67 67 t.text "body"
68 68 t.boolean "replied"
69 69 t.datetime "created_at"
70 70 t.datetime "updated_at"
71 71 end
72 72
73 73 create_table "problems", :force => true do |t|
74 74 t.string "name", :limit => 30
75 75 t.string "full_name"
76 76 t.integer "full_score"
77 77 t.date "date_added"
78 78 t.boolean "available"
79 79 t.string "url"
80 80 t.integer "description_id"
81 81 t.boolean "test_allowed"
82 82 t.boolean "output_only"
83 83 end
84 84
85 85 create_table "rights", :force => true do |t|
86 86 t.string "name"
87 87 t.string "controller"
88 88 t.string "action"
89 89 end
90 90
91 91 create_table "rights_roles", :id => false, :force => true do |t|
92 92 t.integer "right_id"
93 93 t.integer "role_id"
94 94 end
95 95
96 96 add_index "rights_roles", ["role_id"], :name => "index_rights_roles_on_role_id"
97 97
98 98 create_table "roles", :force => true do |t|
99 99 t.string "name"
100 100 end
101 101
102 102 create_table "roles_users", :id => false, :force => true do |t|
103 103 t.integer "role_id"
104 104 t.integer "user_id"
105 105 end
106 106
107 107 add_index "roles_users", ["user_id"], :name => "index_roles_users_on_user_id"
108 108
109 109 create_table "sessions", :force => true do |t|
110 110 t.string "session_id"
111 111 t.text "data"
112 112 t.datetime "updated_at"
113 113 end
114 114
115 115 add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
116 116 add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
117 117
118 118 create_table "sites", :force => true do |t|
119 119 t.string "name"
120 120 t.boolean "started"
121 121 t.datetime "start_time"
122 122 t.datetime "created_at"
123 123 t.datetime "updated_at"
124 124 t.integer "country_id"
125 125 t.string "password"
126 126 end
127 127
128 128 create_table "submissions", :force => true do |t|
129 129 t.integer "user_id"
130 130 t.integer "problem_id"
131 131 t.integer "language_id"
132 132 t.text "source"
133 133 t.binary "binary"
134 134 t.datetime "submitted_at"
135 135 t.datetime "compiled_at"
136 136 t.text "compiler_message"
137 137 t.datetime "graded_at"
138 138 t.integer "points"
139 139 t.text "grader_comment"
140 140 t.integer "number"
141 141 t.string "source_filename"
142 142 end
143 143
144 144 add_index "submissions", ["user_id", "problem_id", "number"], :name => "index_submissions_on_user_id_and_problem_id_and_number", :unique => true
145 145 add_index "submissions", ["user_id", "problem_id"], :name => "index_submissions_on_user_id_and_problem_id"
146 146
147 147 create_table "tasks", :force => true do |t|
148 148 t.integer "submission_id"
149 149 t.datetime "created_at"
150 150 t.integer "status"
151 151 t.datetime "updated_at"
152 152 end
153 153
154 154 create_table "test_requests", :force => true do |t|
155 155 t.integer "user_id"
156 156 t.integer "problem_id"
157 157 t.integer "submission_id"
158 158 t.string "input_file_name"
159 159 t.string "output_file_name"
160 160 t.string "running_stat"
161 161 t.integer "status"
162 162 t.datetime "updated_at"
163 163 t.datetime "submitted_at"
164 164 t.datetime "compiled_at"
165 165 t.text "compiler_message"
166 166 t.datetime "graded_at"
167 167 t.string "grader_comment"
168 168 t.datetime "created_at"
169 169 t.float "running_time"
170 170 t.string "exit_status"
171 171 t.integer "memory_usage"
172 172 end
173 173
174 174 add_index "test_requests", ["user_id", "problem_id"], :name => "index_test_requests_on_user_id_and_problem_id"
175 175
176 176 create_table "users", :force => true do |t|
177 - t.string "login", :limit => 10
178 - t.string "full_name"
179 - t.string "hashed_password"
180 - t.string "salt", :limit => 5
181 - t.string "alias"
182 - t.string "email"
183 - t.integer "site_id"
184 - t.integer "country_id"
185 - t.boolean "activated", :default => false
177 + t.string "login", :limit => 10
178 + t.string "full_name"
179 + t.string "hashed_password"
180 + t.string "salt", :limit => 5
181 + t.string "alias"
182 + t.string "email"
183 + t.integer "site_id"
184 + t.integer "country_id"
185 + t.boolean "activated", :default => false
186 + t.datetime "created_at"
187 + t.datetime "updated_at"
186 188 end
187 189
188 190 add_index "users", ["login"], :name => "index_users_on_login", :unique => true
189 191
190 192 end
@@ -1,180 +1,188
1 1 /* Normal text */
2 2 p {
3 3 font-size: 12px;
4 4 }
5 5
6 6 /* This is the main menu bad*/
7 7 div.userbar {
8 8 border-top: thin solid grey;
9 9 border-bottom: thin solid grey;
10 10 text-align: right;
11 11 font-size: 12px;
12 12 }
13 13
14 14 /* This is the top bar, displaying user's full name */
15 15 div.title {
16 16 font-size: 12px;
17 17 background: #ddffdd;
18 18 border: 1px solid black;
19 19 padding: 2px;
20 20 margin-top: 3px;
21 21 margin-bottom: 5px;
22 22 }
23 23
24 24 div.title span.contest-over-msg {
25 25 font-size: 15px;
26 26 color: red;
27 27 font-weight: bold;
28 28 }
29 29
30 30 div.title table {
31 31 width: 100%;
32 32 }
33 33
34 34 div.title td.left-col {
35 35 text-align: left;
36 36 vertical-align: top;
37 37 }
38 38
39 39 div.title td.right-col {
40 40 text-align: right;
41 41 vertical-align: top;
42 42 }
43 43
44 44 /* Standard table with header and rows with alternating background */
45 45 table.info {
46 46 border: 1px solid black;
47 47 border-collapse: collapse;
48 48 font-size: 12px;
49 49 }
50 50
51 51 table.info th {
52 52 border: 1px solid black;
53 53 }
54 54
55 55 table.info td {
56 56 border-left: 1px solid black;
57 57 border-right: 1px solid black;
58 58 }
59 59
60 60 tr.info-head {
61 61 background: #777777;
62 62 color: white;
63 63 }
64 64
65 65 tr.info-odd {
66 66 background: #dddddd;
67 67 }
68 68
69 69 tr.info-even {
70 70 background: #f0f0f0;
71 71 }
72 72
73 73 /*******************************
74 74 [Main]
75 75 ********************************/
76 76 div.submitbox {
77 77 border: thin solid black;
78 78 padding: 5px;
79 79 color: white;
80 80 background-color: #777777;
81 81 font-weight: bold;
82 82 font-size: 13px;
83 83 }
84 84
85 + div.errorExplanation {
86 + border: 1px dashed black;
87 + color: black;
88 + padding: 5px;
89 + margin-bottom: 5px;
90 + background-color: white;
91 + }
92 +
85 93 /*******************************
86 94 [Settings]
87 95 ********************************/
88 96 table.uinfo {
89 97 border-collapse: collapse;
90 98 border: 1px solid black;
91 99 font-size: 13px;
92 100 }
93 101
94 102 td.uinfo {
95 103 vertical-align: top;
96 104 border: 1px solid black;
97 105 padding: 5px;
98 106 }
99 107
100 108 th.uinfo {
101 109 background: lightgreen;
102 110 vertical-align: top;
103 111 text-align: right;
104 112 border: 1px solid black;
105 113 padding: 5px;
106 114 }
107 115
108 116 /*******************************
109 117 [Submission]
110 118 ********************************/
111 119 div.compilermsgbody {
112 120 font-family: monospace;
113 121 }
114 122
115 123 div.task-menu {
116 124 text-align: center;
117 125 font-size: 13px;
118 126 font-weight: bold;
119 127 border-top: 1px solid black;
120 128 border-bottom: 1px solid black;
121 129 margin-top: 2px;
122 130 margin-bottom: 4px;
123 131 }
124 132
125 133 /*******************************
126 134 [Submission]
127 135 ********************************/
128 136 table.taskdesc {
129 137 border: 1px solid black;
130 138 border-collapse: collapse;
131 139 width: 95%;
132 140 font-size: 13px;
133 141 }
134 142
135 143 table.taskdesc p {
136 144 font-size: 13px;
137 145 }
138 146
139 147 table.taskdesc tr.name {
140 148 border: 1px solid black;
141 149 background: #aaaaaa;
142 150 color: white;
143 151 font-weight: bold;
144 152 font-size: 14px;
145 153 text-align: center;
146 154 }
147 155
148 156 table.taskdesc td.desc-odd {
149 157 padding: 5px;
150 158 padding-left: 20px;
151 159 background: #fefeee;
152 160 }
153 161
154 162 table.taskdesc td.desc-even {
155 163 padding: 5px;
156 164 padding-left: 20px;
157 165 background: #feeefe;
158 166 }
159 167
160 168 /**********************
161 169 [Announcement]
162 170 ***********************/
163 171
164 172 div.announcementbox {
165 173 margin-top: 10px;
166 174 margin-bottom: 10px;
167 175 background: green;
168 176 padding: 1px;
169 177 }
170 178
171 179 div.announcementbox span.title {
172 180 font-weight: bold;
173 181 color: white;
174 182 padding-left: 10px;
175 183 }
176 184
177 185 div.announcement {
178 186 margin: 2px;
179 187 background: white;
180 188 padding: 1px;
@@ -1,4 +1,5
1 1 #!/usr/bin/env ruby
2 2 $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../vendor/plugins/rspec/lib"))
3 + require 'rubygems'
3 4 require 'spec'
4 5 exit ::Spec::Runner::CommandLine.run(::Spec::Runner::OptionParser.parse(ARGV, STDERR, STDOUT))
@@ -1,50 +1,55
1 1
2 2 require File.dirname(__FILE__) + '/../spec_helper'
3 3
4 4 describe MainController do
5 5
6 6 before(:each) do
7 - @problem = mock(Problem, :name => 'test')
7 + @problem = mock(Problem, :name => 'test', :output_only => false)
8 8 @language = mock(Language, :name => 'cpp', :ext => 'cpp')
9 9 @submission = mock(Submission,
10 10 :id => 1,
11 11 :user_id => 1,
12 12 :problem => @problem,
13 13 :language => @language,
14 14 :source => 'sample source',
15 15 :compiler_message => 'none')
16 16 @user = mock(User, :id => 1, :login => 'john')
17 + @another_user = mock(User, :id => 2, :login => 'mary')
17 18 end
18 19
19 20 it "should redirect user to login page when unlogged-in user try to access main/list" do
20 21 get 'list'
21 22 response.should redirect_to(:action => 'login')
22 23 end
23 24
24 25 it "should let user sees her own source" do
25 26 Submission.should_receive(:find).with(@submission.id.to_s).and_return(@submission)
27 + User.should_receive(:find).with(1).and_return(@user)
26 28 get 'source', {:id => @submission.id}, {:user_id => 1}
27 29 response.should be_success
28 30 end
29 31
30 32 it "should let user sees her own compiler message" do
31 33 Submission.should_receive(:find).with(@submission.id.to_s).and_return(@submission)
34 + User.should_receive(:find).with(1).and_return(@user)
32 35 get 'compiler_msg', {:id => @submission.id}, {:user_id => 1}
33 36 response.should be_success
34 37 end
35 38
36 39 it "should not let user sees other user's source" do
37 40 Submission.should_receive(:find).with(@submission.id.to_s).and_return(@submission)
41 + User.should_receive(:find).with(2).and_return(@another_user)
38 42 get 'source', {:id => @submission.id}, {:user_id => 2}
39 43 flash[:notice].should =~ /[Ee]rror/
40 44 response.should redirect_to(:action => 'list')
41 45 end
42 46
43 47 it "should not let user sees other user's compiler message" do
44 48 Submission.should_receive(:find).with(@submission.id.to_s).and_return(@submission)
49 + User.should_receive(:find).with(2).and_return(@another_user)
45 50 get 'compiler_msg', {:id => @submission.id}, {:user_id => 2}
46 51 flash[:notice].should =~ /[Ee]rror/
47 52 response.should redirect_to(:action => 'list')
48 53 end
49 54
50 55 end
@@ -1,28 +1,28
1 1
2 2 require File.dirname(__FILE__) + '/../spec_helper'
3 3
4 4 describe TestController do
5 5
6 6 before(:each) do
7 7 @john = mock(User, :id => "1", :login => 'john')
8 8 @john_result = mock(TestRequest, :id => "1", :user_id => @john.id)
9 9 @mary_result = mock(TestRequest, :id => "2", :user_id => @john.id + '1')
10 - User.should_receive(:find).with(@john.id).and_return(@john)
10 + User.should_receive(:find).at_least(:once).with(@john.id).and_return(@john)
11 11 end
12 12
13 13 it "should let user see her testing result" do
14 14 TestRequest.should_receive(:find).with(@john_result.id).
15 15 and_return(@john_result)
16 16 get 'result', {:id => @john_result.id}, {:user_id => @john.id}
17 17 response.should be_success
18 18 end
19 19
20 20 it "should not let user see other's testing result" do
21 21 TestRequest.should_receive(:find).with(@mary_result.id).
22 22 and_return(@mary_result)
23 23 get 'result', {:id => @mary_result.id}, {:user_id => @john.id}
24 24 response.should redirect_to(:action => 'index')
25 25 end
26 26
27 27 end
28 28
@@ -1,43 +1,48
1 1
2 2 require File.dirname(__FILE__) + '/../spec_helper'
3 3
4 4 describe Site do
5 5
6 6 before(:each) do
7 - start_time = Time.local(2008,5,10,9,00)
7 + start_time = Time.local(2008,5,10,9,00).gmtime
8 8 @site = Site.new({:name => 'Test site',
9 9 :started => true,
10 10 :start_time => start_time })
11 + @site.stub!(:start_time).
12 + any_number_of_times.
13 + and_return(start_time)
14 + @site.stub!(:started).any_number_of_times.and_return(true)
11 15 end
12 16
13 17 it "should report that the contest is not finished when the contest time limit is not set" do
14 18 Configuration.should_receive(:[]).with('contest.time_limit').
15 19 and_return('unlimit')
16 - Time.should_not_receive(:now)
17 20 @site.finished?.should == false
18 21 end
19 22
20 23 it "should report that the contest is finished when the contest is over" do
21 24 Configuration.should_receive(:[]).with('contest.time_limit').
22 - and_return('5:00')
23 - Time.should_receive(:now).and_return(Time.local(2008,5,10,14,01))
24 - @site.finished?.should == true
25 - end
25 + and_return('5:00')
26 + Time.stub!(:now).
27 + and_return(Time.local(2008,5,10,14,01).gmtime)
28 + @site.finished?.should == true end
26 29
27 30 it "should report if the contest is finished correctly, when the contest is over, and the contest time contains some minutes" do
28 31 Configuration.should_receive(:[]).twice.with('contest.time_limit').
29 32 and_return('5:15')
30 - Time.should_receive(:now).
31 - and_return(Time.local(2008,5,10,14,14),Time.local(2008,5,10,14,16))
33 + Time.stub!(:now).
34 + and_return(Time.local(2008,5,10,14,14))
32 35 @site.finished?.should == false
36 + Time.stub!(:now).
37 + and_return(Time.local(2008,5,10,14,16))
33 38 @site.finished?.should == true
34 39 end
35 40
36 41 it "should report that the contest is not finished, when the time is exactly at the finish time" do
37 42 Configuration.should_receive(:[]).with('contest.time_limit').
38 43 and_return('5:00')
39 - Time.should_receive(:now).and_return(Time.local(2008,5,10,14,00))
44 + Time.stub!(:now).and_return(Time.local(2008,5,10,14,00))
40 45 @site.finished?.should == false
41 46 end
42 47
43 48 end
@@ -1,63 +1,104
1 1
2 2 require File.dirname(__FILE__) + '/../spec_helper'
3 3
4 4 describe User do
5 5
6 6 before(:each) do
7 7 @password = "hello"
8 8 @salt = "123"
9 9 @john = stub_model(User, :salt => @salt,
10 10 :hashed_password => User.encrypt(@password,@salt))
11 11 end
12 12
13 13 it "should be authenticated if activated" do
14 14 @john.should_receive(:activated).and_return(true)
15 15 @john.authenticated?(@password).should == true
16 16 end
17 17
18 18 it "should not be authenticated if inactivated" do
19 19 @john.should_receive(:activated).and_return(false)
20 20 @john.authenticated?(@password).should == false
21 21 end
22 22
23 23 it "should not be authenticated if incorrect password is provided" do
24 24 @john.should_receive(:activated).and_return(true)
25 25 @john.should_receive(:hashed_password).and_return("byebye")
26 26 @john.authenticated?(@password).should == false
27 27 end
28 28
29 29 end
30 30
31 31 describe User, "during registration" do
32 32
33 33 class User
34 34 public :encrypt_new_password
35 35 end
36 36
37 37 before(:each) do
38 38 @john = User.new(:login => 'john', :password => 'hello')
39 39 @john.encrypt_new_password
40 40 end
41 41
42 42 it "should produce and accept activation key" do
43 43 activation_key = @john.activation_key
44 44
45 45 @john.verify_activation_key(activation_key).should == true
46 46 end
47 47
48 48 it "should not accept invalid activation key" do
49 49 @john.verify_activation_key("12345").should == false
50 50 end
51 -
51 + end
52 +
53 + describe User, "when re-register with the same e-mail" do
54 +
55 + before(:each) do
56 + @mary_email = 'mary@in.th'
57 +
58 + @time_first_register = Time.local(2008,5,10,9,00).gmtime
59 +
60 + @mary_first = mock_model(User,
61 + :login => 'mary1',
62 + :password => 'hello',
63 + :email => @mary_email,
64 + :created_at => @time_first_register)
65 + @mary_second = User.new(:login => 'mary2',
66 + :password => 'hello',
67 + :email => @mary_email)
68 + User.stub!(:find_by_email).
69 + with(@mary_email, {:order => "created_at DESC"}).
70 + and_return(@mary_first)
71 + end
72 +
73 + class User
74 + public :enough_time_interval_between_same_email_registrations
75 + end
76 +
77 + it "should not be allowed if the time interval is less than 5 mins" do
78 + time_now = @time_first_register + 4.minutes
79 + Time.stub!(:now).and_return(time_now)
80 +
81 + @mary_second.enough_time_interval_between_same_email_registrations
82 + @mary_second.errors.length.should_not be_zero
83 + end
84 +
85 + it "should be allowed if the time interval is more than 5 mins" do
86 + time_now = @time_first_register + 6.minutes
87 + Time.stub!(:now).and_return(time_now)
88 +
89 + @mary_second.enough_time_interval_between_same_email_registrations
90 + @mary_second.errors.length.should be_zero
91 + end
92 +
52 93 end
53 94
54 95 describe User, "as a class" do
55 96
56 97 it "should be able to generate random password" do
57 98 password1 = User.random_password
58 99 password2 = User.random_password
59 100
60 101 password1.should_not == password2
61 102 end
62 103
63 104 end
You need to be logged in to leave comments. Login now