Finish creation and signin flow

This commit is contained in:
Kyle Drake 2013-05-25 17:09:48 -07:00
parent 4ddd244588
commit 0aaf894fa3
13 changed files with 182 additions and 43 deletions

36
app.rb
View file

@ -1,10 +1,17 @@
require './environment.rb' require './environment.rb'
use Rack::Session::Cookie, key: 'neocities',
path: '/',
expire_after: 31556926, # one year in seconds
secret: $config['session_secret']
get '/' do get '/' do
dashboard_if_signed_in
slim :index slim :index
end end
get '/new' do get '/new' do
dashboard_if_signed_in
@site = Site.new @site = Site.new
slim :'new' slim :'new'
end end
@ -14,23 +21,17 @@ get '/dashboard' do
end end
get '/signin' do get '/signin' do
dashboard_if_signed_in
slim :'signin' slim :'signin'
end end
post '/create' do 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? if @site.valid?
DB.transaction { @site.save }
@server = Server.with_slots_available session[:id] = @site.id
if @server.nil?
raise 'no slots available'
end
@site.server = @server
@site.save
session[:username] = @site.username
redirect '/dashboard' redirect '/dashboard'
else else
slim :'/new' slim :'/new'
@ -38,8 +39,10 @@ post '/create' do
end end
post '/signin' do post '/signin' do
dashboard_if_signed_in
if Site.valid_login? params[:username], params[:password] if Site.valid_login? params[:username], params[:password]
session[:username] = params[:username] site = Site[username: params[:username]]
session[:id] = site.id
redirect '/dashboard' redirect '/dashboard'
else else
flash[:error] = 'Invalid login.' flash[:error] = 'Invalid login.'
@ -49,8 +52,7 @@ end
get '/signout' do get '/signout' do
require_login require_login
session[:username] = nil session[:id] = nil
session[:timezone] = nil
redirect '/' redirect '/'
end end
@ -63,5 +65,9 @@ def require_login
end end
def signed_in? def signed_in?
!session[:username].nil? !session[:id].nil?
end
def current_site
@site ||= Site[id: session[:id]]
end end

View file

@ -1,2 +1,3 @@
development: development:
database: 'postgres://localhost/neocities' database: 'postgres://localhost/neocities'
session_secret: SETSOMETHINGHERE

View file

@ -1,7 +1,8 @@
Sequel.migration do Sequel.migration do
up { up {
DB.create_table! :sites do DB.create_table! :sites do
String :username, primary_key: true primary_key :id
String :username
String :email String :email
String :password String :password
Integer :server_id Integer :server_id

View file

@ -3,7 +3,6 @@ Sequel.migration do
DB.create_table! :tags do DB.create_table! :tags do
primary_key :id primary_key :id
String :name String :name
DateTime :created_at
end end
} }

View file

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

View file

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

View file

@ -1,8 +1,7 @@
class Site < Sequel::Model class Site < Sequel::Model
MINIMUM_PASSWORD_LENGTH = 5 MINIMUM_PASSWORD_LENGTH = 5
unrestrict_primary_key
many_to_one :server many_to_one :server
many_to_many :tags
class << self class << self
def valid_login?(username, plaintext) 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) values[:password] = BCrypt::Password.create plaintext, cost: (self.class.bcrypt_cost || BCrypt::Engine::DEFAULT_COST)
end 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 def validate
super 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.' errors.add :username, 'A valid username is required.'
end end

3
models/tag.rb Normal file
View file

@ -0,0 +1,3 @@
class Tag < Sequel::Model
many_to_many :sites
end

View file

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

View file

@ -0,0 +1,4 @@
Fabricator(:site) do
username { Faker::Internet.email }
password { 'abcde' }
end

View file

@ -1,4 +1,4 @@
.row .row
.span12 .span12
h1 Dashboard! h1 Dashboard

View file

@ -19,7 +19,7 @@ html
a.brand href="/" NeoCities a.brand href="/" NeoCities
ul.nav.pull-right ul.nav.pull-right
- if signed_in? - 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 li: a href="/signout" style="color: #B94A48" Signout
- else - else
li: a href="/signin" <b>Sign in</b> li: a href="/signin" <b>Sign in</b>

View file

@ -3,7 +3,7 @@
.row .row
.span8.offset2 .span8.offset2
.alert.alert-block.alert-error .alert.alert-block.alert-error
p Please correct the following errors: p There were errors creating your home page:
- @site.errors.each do |error| - @site.errors.each do |error|
p = error.last.first p = error.last.first
@ -14,17 +14,17 @@
.row .row
.span6 .span6
p First, enter a username. This will be used to login to your site.<br>It will also be the path of your web site. p First, enter a username. This will also be used as your site path.<br><b>Do not forget this, it will be used to sign in to and manage your home page.</b><br>It cannot contain spaces, and can only use the following characters: a-z A-Z 0-9 . _ -
.row .row
.span1 .span1
h5 Username h5 Username
.span6 .span6
p http://neocities.org/<input name="username" type="text" placeholder="yourusername"> p http://neocities.org/<input name="username" type="text" placeholder="yourusername" value="#{@site.username}">
.row .row
.span6 .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 .row
.span1 .span1
@ -34,26 +34,30 @@
.row .row
.span6 .span6
p Now you can enter an e-mail address. You don't have to provide one, but <b>we will not be able to reset your password without it, so don't lose your username and password if you leave this blank!</b> 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 <b>we will not be able to reset your password without it, so don't lose your username and password if you leave this blank!</b>
.row .row
.span1 .span1
h5 Email h5 Email
.span6 .span6
input name="email" type="text" placeholder="youremail@example.com" input name="email" type="text" placeholder="youremail@example.com" value="#{@site.email}"
.row .row
.span6 .span6
p p
| Enter some tags! Tags will allow others to find your site based on your interests, or your site's theme. <b>Separate multiple tags with commas</b>. 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. <b>Separate multiple tags with commas</b>. 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 .row
.span1 .span1
h5 Tags h5 Tags
.span6 .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" .row style="margin-top: 30px"
.span3.offset1 .span3.offset1
input.btn.btn-success.btn-large type="submit" value="Create Home Page" input.btn.btn-success.btn-large type="submit" value="Create Home Page"