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