diff --git a/app.rb b/app.rb index 90226142..c7513990 100644 --- a/app.rb +++ b/app.rb @@ -1,10 +1,17 @@ require './environment.rb' +use Rack::Session::Cookie, key: 'neocities', + path: '/', + expire_after: 31556926, # one year in seconds + secret: $config['session_secret'] + get '/' do + dashboard_if_signed_in slim :index end get '/new' do + dashboard_if_signed_in @site = Site.new slim :'new' end @@ -14,23 +21,17 @@ get '/dashboard' do end get '/signin' do + dashboard_if_signed_in slim :'signin' end post '/create' do - @site = Site.new username: params[:username], password: params[:password], email: params[:email] + dashboard_if_signed_in + @site = Site.new username: params[:username], password: params[:password], email: params[:email], new_tags: params[:tags] if @site.valid? + DB.transaction { @site.save } - @server = Server.with_slots_available - - if @server.nil? - raise 'no slots available' - end - - @site.server = @server - @site.save - - session[:username] = @site.username + session[:id] = @site.id redirect '/dashboard' else slim :'/new' @@ -38,8 +39,10 @@ post '/create' do end post '/signin' do + dashboard_if_signed_in if Site.valid_login? params[:username], params[:password] - session[:username] = params[:username] + site = Site[username: params[:username]] + session[:id] = site.id redirect '/dashboard' else flash[:error] = 'Invalid login.' @@ -49,8 +52,7 @@ end get '/signout' do require_login - session[:username] = nil - session[:timezone] = nil + session[:id] = nil redirect '/' end @@ -63,5 +65,9 @@ def require_login end def signed_in? - !session[:username].nil? + !session[:id].nil? +end + +def current_site + @site ||= Site[id: session[:id]] end \ No newline at end of file diff --git a/config.yml.template b/config.yml.template index efb8a6a6..6728f339 100644 --- a/config.yml.template +++ b/config.yml.template @@ -1,2 +1,3 @@ development: - database: 'postgres://localhost/neocities' \ No newline at end of file + database: 'postgres://localhost/neocities' + session_secret: SETSOMETHINGHERE \ No newline at end of file diff --git a/migrations/001_create_sites.rb b/migrations/001_create_sites.rb index c0283ccd..95d088f1 100644 --- a/migrations/001_create_sites.rb +++ b/migrations/001_create_sites.rb @@ -1,7 +1,8 @@ Sequel.migration do up { DB.create_table! :sites do - String :username, primary_key: true + primary_key :id + String :username String :email String :password Integer :server_id diff --git a/migrations/003_create_tags.rb b/migrations/003_create_tags.rb index 14765bba..1a98b216 100644 --- a/migrations/003_create_tags.rb +++ b/migrations/003_create_tags.rb @@ -3,7 +3,6 @@ Sequel.migration do DB.create_table! :tags do primary_key :id String :name - DateTime :created_at end } diff --git a/migrations/004_create_site_tags.rb b/migrations/004_create_site_tags.rb deleted file mode 100644 index 66d61b72..00000000 --- a/migrations/004_create_site_tags.rb +++ /dev/null @@ -1,12 +0,0 @@ -Sequel.migration do - up { - DB.create_table! :site_tags do - Integer :site_id - Integer :tag_id - end - } - - down { - DB.drop_table :site_tags - } -end \ No newline at end of file diff --git a/migrations/004_create_sites_tags.rb b/migrations/004_create_sites_tags.rb new file mode 100644 index 00000000..beaf0fe1 --- /dev/null +++ b/migrations/004_create_sites_tags.rb @@ -0,0 +1,12 @@ +Sequel.migration do + up { + DB.create_table! :sites_tags do + foreign_key :site_id, :sites + foreign_key :tag_id, :tags + end + } + + down { + DB.drop_table :sites_tags + } +end \ No newline at end of file diff --git a/models/site.rb b/models/site.rb index d104dd4f..fa9d9740 100644 --- a/models/site.rb +++ b/models/site.rb @@ -1,8 +1,7 @@ class Site < Sequel::Model MINIMUM_PASSWORD_LENGTH = 5 - - unrestrict_primary_key many_to_one :server + many_to_many :tags class << self def valid_login?(username, plaintext) @@ -30,10 +29,37 @@ class Site < Sequel::Model values[:password] = BCrypt::Password.create plaintext, cost: (self.class.bcrypt_cost || BCrypt::Engine::DEFAULT_COST) end + def after_save + if @new_tag_strings + @new_tag_strings.each do |new_tag_string| + add_tag Tag[name: new_tag_string] || Tag.create(name: new_tag_string) + end + end + end + + def after_create + DB['update servers set slots_available=slots_available-1 where id=?', self.server.id].first + end + + def new_tags=(tags_string) + tags_string.gsub! /[^a-zA-Z0-9, ]/, '' + tags = tags_string.split ',' + tags.collect! {|c| (c.match(/^\w+\s\w+/) || c.match(/^\w+/)).to_s } + @new_tag_strings = tags + end + + def before_validation + self.server ||= Server.with_slots_available + end + def validate super - if values[:username].nil? || values[:username].empty? + if server.nil? + errors.add :over_capacity, 'We are currently at capacity, and cannot create your home page. We will fix this shortly. Please come back later and try again, our apologies.' + end + + if values[:username].nil? || values[:username].empty? || values[:username].match(/[^\w.-]/i) errors.add :username, 'A valid username is required.' end diff --git a/models/tag.rb b/models/tag.rb new file mode 100644 index 00000000..4dd917f6 --- /dev/null +++ b/models/tag.rb @@ -0,0 +1,3 @@ +class Tag < Sequel::Model + many_to_many :sites +end \ No newline at end of file diff --git a/tests/app_test.rb b/tests/app_test.rb index e69de29b..45813320 100644 --- a/tests/app_test.rb +++ b/tests/app_test.rb @@ -0,0 +1,95 @@ +require_relative './environment' + +include Rack::Test::Methods + +def app; App end +def status; last_response.status end +def headers; last_response.headers end +def body; last_response.body end + +SimpleCov.command_name 'minitest' + +describe 'index' do + it 'loads' do + get '/' + status.must_equal 200 + end +end + +describe 'signin' do + it 'fails for missing login' do + post '/signin', username: 'derpie', password: 'lol' + fail_signin + end + + it 'fails for bad password' do + @site = Fabricate :site + post '/signin', username: @site.username, password: 'derp' + fail_signin + end + + it 'fails for no input' do + post '/signin' + fail_signin + end + + it 'succeeds for valid input' do + password = '1tw0rkz' + @account = Fabricate :account, password: password + post '/accounts/signin', username: @account.email, password: password + headers['Location'].must_equal 'http://example.org/dashboard' + mock_dashboard_calls @account.email + get '/dashboard' + body.must_match /Dashboard/ + end +end + +describe 'account creation' do + it 'fails for no input' do + post '/accounts/create' + status.must_equal 200 + body.must_match /There were some errors.+Valid email address is required.+Password must be/ + end + + it 'fails with invalid email' do + post '/accounts/create', email: 'derplol' + status.must_equal 200 + body.must_match /errors.+valid email/i + end + + it 'fails with invalid password' do + post '/accounts/create', 'email@example.com', password: 'sdd' + status.must_equal 200 + body.must_match /errors.+Password must be at least #{Account::MINIMUM_PASSWORD_LENGTH} characters/i + end + + it 'succeeds with valid info' do + account_attributes = Fabricate.attributes_for :account + + mock_dashboard_calls account_attributes[:email] + + post '/accounts/create', account_attributes + status.must_equal 302 + headers['Location'].must_equal 'http://example.org/dashboard' + + get '/dashboard' + body.must_match /Dashboard/ + end +end + +describe 'temporary account login' do +end + +def fail_signin + headers['Location'].must_equal 'http://example.org/' + get '/' + body.must_match /invalid signin/i +end + +def api_url + uri = Addressable::URI.parse $config['bitcoind_rpchost'] ? $config['bitcoind_rpchost'] : 'http://localhost' + uri.port = 8332 if uri.port.nil? + uri.user = $config['bitcoind_rpcuser'] if uri.user.nil? + uri.password = $config['bitcoind_rpcpassword'] if uri.password.nil? + "#{uri.to_s}/" +end \ No newline at end of file diff --git a/tests/fabricators/site_fabricator.rb b/tests/fabricators/site_fabricator.rb new file mode 100644 index 00000000..db81e5b7 --- /dev/null +++ b/tests/fabricators/site_fabricator.rb @@ -0,0 +1,4 @@ +Fabricator(:site) do + username { Faker::Internet.email } + password { 'abcde' } +end \ No newline at end of file diff --git a/views/dashboard.slim b/views/dashboard.slim index 47e8497d..7f22ff0d 100644 --- a/views/dashboard.slim +++ b/views/dashboard.slim @@ -1,4 +1,4 @@ .row .span12 - h1 Dashboard! \ No newline at end of file + h1 Dashboard \ No newline at end of file diff --git a/views/layout.slim b/views/layout.slim index 5d337d49..1b06404b 100644 --- a/views/layout.slim +++ b/views/layout.slim @@ -19,7 +19,7 @@ html a.brand href="/" NeoCities ul.nav.pull-right - if signed_in? - li.navbar-text: strong style="color: #7AB800" #{session[:username]} + li.navbar-text: strong style="color: #7AB800" #{current_site.username} li: a href="/signout" style="color: #B94A48" Signout - else li: a href="/signin" Sign in diff --git a/views/new.slim b/views/new.slim index c028362b..6f70da5b 100644 --- a/views/new.slim +++ b/views/new.slim @@ -3,7 +3,7 @@ .row .span8.offset2 .alert.alert-block.alert-error - p Please correct the following errors: + p There were errors creating your home page: - @site.errors.each do |error| p = error.last.first @@ -14,17 +14,17 @@ .row .span6 - p First, enter a username. This will be used to login to your site.
It will also be the path of your web site. + p First, enter a username. This will also be used as your site path.
Do not forget this, it will be used to sign in to and manage your home page.
It cannot contain spaces, and can only use the following characters: a-z A-Z 0-9 . _ - .row .span1 h5 Username .span6 - p http://neocities.org/ + p http://neocities.org/ .row .span6 - p Next, enter a password. This will be used to allow you to login. Minimum 5 characters. + p Next, enter a password. This will be used to allow you to login. Minimum 5 characters. If you don't make it a good password, Dade Murphy from the movie Hackers will come in and steal your "garbage files". .row .span1 @@ -34,26 +34,30 @@ .row .span6 - p Now you can enter an e-mail address. You don't have to provide one, but we will not be able to reset your password without it, so don't lose your username and password if you leave this blank! + p Now you can enter an e-mail address. Your e-mail address is private and we will not show it to anyone for any reason. You don't have to provide one, but we will not be able to reset your password without it, so don't lose your username and password if you leave this blank! .row .span1 h5 Email .span6 - input name="email" type="text" placeholder="youremail@example.com" + input name="email" type="text" placeholder="youremail@example.com" value="#{@site.email}" .row .span6 p - | Enter some tags! Tags will allow others to find your site based on your interests, or your site's theme. Separate multiple tags with commas. Don't think too hard about this, you can change them later. You can have a maximum of ten tags, and there is a two word per tag maximum. + | You can optionally enter some tags! Tags will allow others to find your site based on your interests, or your site's theme. Separate multiple tags with commas. Don't think too hard about this, you can change them later. You can have a maximum of ten tags, and there is a two word per tag maximum (extra words in a tag will be removed). .row .span1 h5 Tags .span6 - p: input name="tags" type="text" style="width: 400px" placeholder="pokemon, video games, bulbasaur" + p: input name="tags" type="text" style="width: 400px" placeholder="pokemon, video games, bulbasaur" value="#{params[:tags]}" + .row + .span6 + h3 You're done. Just click the button below! + .row style="margin-top: 30px" .span3.offset1 input.btn.btn-success.btn-large type="submit" value="Create Home Page" \ No newline at end of file