<%== error.last.first %>
- <% end %> -diff --git a/app.rb b/app.rb index 556894a0..2521f35d 100644 --- a/app.rb +++ b/app.rb @@ -125,6 +125,23 @@ post '/site/:username/set_editor_theme' do 'ok' end +post '/site/create_child' do + require_login + site = Site.new + + site.parent_site_id = current_site.id + site.username = params[:username] + + if site.valid? + site.save + flash[:success] = 'Your new site has been created!' + redirect '/settings#sites' + else + flash[:error] = site.errors.first.last.first + redirect '/settings#sites' + end +end + post '/site/:username/comment' do |username| require_login @@ -547,7 +564,7 @@ post '/settings/profile' do profile_comments_enabled: params[:site][:profile_comments_enabled] ) flash[:success] = 'Profile settings changed.' - redirect '/settings' + redirect '/settings#profile' end post '/settings/ssl' do @@ -655,7 +672,7 @@ post '/signin' do dashboard_if_signed_in if Site.valid_login? params[:username], params[:password] - site = Site[username: params[:username]] + site = Site.get_with_identifier params[:username] if site.is_banned flash[:error] = 'Invalid login.' @@ -692,7 +709,7 @@ post '/change_password' do if !Site.valid_login?(current_site.username, params[:current_password]) current_site.errors.add :password, 'Your provided password does not match the current one.' - halt erb(:'settings') + redirect '/settings#password' end current_site.password = params[:new_password] @@ -705,9 +722,10 @@ post '/change_password' do if current_site.errors.empty? current_site.save_changes flash[:success] = 'Successfully changed password.' - redirect '/settings' + redirect '/settings#password' else - halt erb(:'settings') + flash[:error] = current_site.errors.first.last.first + redirect '/settings#password' end end @@ -715,8 +733,8 @@ post '/change_email' do require_login if params[:email] == current_site.email - current_site.errors.add :email, 'You are already using this email address for this account.' - halt erb(:settings) + flash[:error] = 'You are already using this email address for this account.' + redirect '/settings#email' end current_site.email = params[:email] @@ -727,11 +745,11 @@ post '/change_email' do current_site.save_changes send_confirmation_email flash[:success] = 'Successfully changed email. We have sent a confirmation email, please use it to confirm your email address.' - redirect '/settings' + redirect '/settings#email' end - current_site.reload - erb :settings + flash[:error] = current_site.errors.first.last.first + redirect '/settings#email' end post '/change_name' do @@ -740,12 +758,12 @@ post '/change_name' do if params[:name] == nil || params[:name] == '' flash[:error] = 'Name cannot be blank.' - redirect '/settings' + redirect '/settings#username' end if old_username == params[:name] flash[:error] = 'You already have this name.' - redirect '/settings' + redirect '/settings#username' end old_host = current_site.host @@ -764,9 +782,10 @@ post '/change_name' do end flash[:success] = "Site/user name has been changed. You will need to use this name to login, don't forget it." - redirect '/settings' + redirect '/settings#username' else - halt erb(:'settings') + flash[:error] = current_site.errors.first.last.first + redirect '/settings#username' end end @@ -774,7 +793,7 @@ post '/change_nsfw' do require_login current_site.update is_nsfw: params[:is_nsfw] flash[:success] = current_site.is_nsfw ? 'Marked 18+' : 'Unmarked 18+' - redirect '/settings' + redirect '/settings#nsfw' end post '/site_files/create_page' do diff --git a/ext/numeric.rb b/ext/numeric.rb index adc6b5b7..293e89d5 100644 --- a/ext/numeric.rb +++ b/ext/numeric.rb @@ -1,5 +1,15 @@ class Numeric + ONE_MEGABYTE = 1048576 + def roundup(nearest=10) self % nearest == 0 ? self : self + nearest - (self % nearest) end -end + + def to_mb + self/ONE_MEGABYTE.to_f + end + + def to_space_pretty + "#{(self.to_f / ONE_MEGABYTE).round(2).to_s} MB" + end +end \ No newline at end of file diff --git a/migrations/046_add_site_parent_id.rb b/migrations/046_add_site_parent_id.rb new file mode 100644 index 00000000..1830dec3 --- /dev/null +++ b/migrations/046_add_site_parent_id.rb @@ -0,0 +1,9 @@ +Sequel.migration do + up { + DB.add_column :sites, :parent_site_id, :integer, index: true + } + + down { + DB.drop_column :sites, :parent_site_id + } +end \ No newline at end of file diff --git a/models/site.rb b/models/site.rb index 96ea450b..e6d684e4 100644 --- a/models/site.rb +++ b/models/site.rb @@ -34,11 +34,8 @@ class Site < Sequel::Model geojson csv tsv mf ico pdf asc key pgp xml mid midi } - ONE_MEGABYTE_IN_BYTES = 1048576 - FREE_MAXIMUM_IN_MEGABYTES = 20 - SUPPORTER_MAXIMUM_IN_MEGABYTES = 1024 - FREE_MAXIMUM_IN_BYTES = FREE_MAXIMUM_IN_MEGABYTES * ONE_MEGABYTE_IN_BYTES - SUPPORTER_MAXIMUM_IN_BYTES = SUPPORTER_MAXIMUM_IN_MEGABYTES * ONE_MEGABYTE_IN_BYTES + FREE_MAXIMUM = 20 * Numeric::ONE_MEGABYTE + SUPPORTER_MAXIMUM = 1000 * Numeric::ONE_MEGABYTE MINIMUM_PASSWORD_LENGTH = 5 BAD_USERNAME_REGEX = /[^\w-]/i @@ -95,6 +92,7 @@ class Site < Sequel::Model SUGGESTIONS_LIMIT = 32 SUGGESTIONS_VIEWS_MIN = 500 + CHILD_SITES_MAX = 1000 PLAN_FEATURES[:catbus] = PLAN_FEATURES[:fatcat].merge( name: 'Cat Bus', @@ -147,9 +145,33 @@ class Site < Sequel::Model one_to_many :site_changes + many_to_one :parent, :key => :parent_site_id, :class => self + one_to_many :children, :key => :parent_site_id, :class => self + + def account_sites + if parent? + sites = [self] + children + else + sites = [parent] + parent.children + end + + sites.compact + end + + def other_sites + if parent? + return children + else + sites = (parent + children) + sites.delete self + sites + end + end + class << self - def valid_login?(username, plaintext) - site = self[username: username] + def valid_login?(username_or_email, plaintext) + site = get_with_identifier username_or_email + return false if site.nil? site.valid_password? plaintext end @@ -161,6 +183,14 @@ class Site < Sequel::Model def bcrypt_cost=(cost) @bcrypt_cost = cost end + + def get_with_identifier(username_or_email) + if username_or_email =~ /@/ + site = self.where(email: username_or_email).where(parent_site_id: nil).first + else + site = self[username: username_or_email] + end + end end def self.banned_ip?(ip) @@ -317,6 +347,12 @@ class Site < Sequel::Model def commenting_allowed? return true if commenting_allowed + if supporter? + set commenting_allowed: true + save_changes validate: false + return true + end + if events_dataset.exclude(site_change_id: nil).count >= COMMENTING_ALLOWED_UPDATED_COUNT && created_at < Time.now - 604800 set commenting_allowed: true @@ -554,6 +590,10 @@ class Site < Sequel::Model super end + def parent? + parent_site_id.nil? + end + # def after_destroy # FileUtils.rm_rf files_path # super @@ -576,7 +616,7 @@ class Site < Sequel::Model end # Check that email has been provided - if values[:email].empty? + if parent? && values[:email].empty? errors.add :email, 'An email address is required.' end @@ -586,12 +626,12 @@ class Site < Sequel::Model email_check.exclude!(id: self.id) unless new? email_check = email_check.first - if email_check && email_check.id != self.id + if parent? && email_check && email_check.id != self.id errors.add :email, 'This email address already exists on Neocities, please use your existing account instead of creating a new one.' end end - unless values[:email] =~ EMAIL_SANITY_REGEX + if parent? && (values[:email] =~ EMAIL_SANITY_REGEX).nil? errors.add :email, 'A valid email address is required.' end @@ -604,7 +644,7 @@ class Site < Sequel::Model end end - if values[:password].nil? || (@password_length && @password_length < MINIMUM_PASSWORD_LENGTH) + if parent? && (values[:password].nil? || (@password_length && @password_length < MINIMUM_PASSWORD_LENGTH)) errors.add :password, "Password must be at least #{MINIMUM_PASSWORD_LENGTH} characters." end @@ -622,6 +662,10 @@ class Site < Sequel::Model if !site.nil? && site.id != self.id errors.add :domain, "Domain provided is already being used by another site, please choose another." end + + if new? && !parent? && CHILD_SITE_MAX == children_dataset.count + errors.add :child_site_id, "Cannot add child site, exceeds #{CHILD_SITE_MAX} limit." + end end if @new_tags_string @@ -715,39 +759,33 @@ class Site < Sequel::Model list.select {|f| f[:is_directory] == false}.sort_by{|f| f[:name].downcase} end - def file_size_too_large?(size_in_bytes) - return true if size_in_bytes + used_space_in_bytes > maximum_space_in_bytes + def file_size_too_large?(size) + return true if size + used_space > maximum_space false end - def used_space_in_bytes + def used_space space = Dir.glob(File.join(files_path, '*')).collect {|p| File.size(p)}.inject {|sum,x| sum += x} space.nil? ? 0 : space end - def used_space_in_megabytes - (used_space_in_bytes.to_f / self.class::ONE_MEGABYTE_IN_BYTES).round(2) + def total_used_space + total = 0 + account_sites.each {|s| total += s.used_space} + total end - def available_space_in_bytes - remaining = maximum_space_in_bytes - used_space_in_bytes + def remaining_space + remaining = maximum_space - total_used_space remaining < 0 ? 0 : remaining end - def available_space_in_megabytes - (available_space_in_bytes.to_f / self.class::ONE_MEGABYTE_IN_BYTES).round(2) - end - - def maximum_space_in_bytes - supporter? ? self.class::SUPPORTER_MAXIMUM_IN_BYTES : self.class::FREE_MAXIMUM_IN_BYTES - end - - def maximum_space_in_megabytes - supporter? ? self.class::SUPPORTER_MAXIMUM_IN_MEGABYTES : self.class::FREE_MAXIMUM_IN_MEGABYTES + def maximum_space + (parent? ? self : parent).supporter? ? SUPPORTER_MAXIMUM : FREE_MAXIMUM end def space_percentage_used - ((used_space_in_bytes.to_f / maximum_space_in_bytes) * 100).round(1) + ((total_used_space.to_f / maximum_space) * 100).round(1) end # This returns true even if they end their support plan. diff --git a/tests/acceptance/signin_tests.rb b/tests/acceptance/signin_tests.rb index 78e13e46..a990870e 100644 --- a/tests/acceptance/signin_tests.rb +++ b/tests/acceptance/signin_tests.rb @@ -18,7 +18,7 @@ describe 'signin' do Capybara.reset_sessions! end - it 'fails for invalid login' do + it 'fails for invalid signin' do visit '/' click_link 'Sign In' page.must_have_content 'Welcome Back' @@ -27,7 +27,7 @@ describe 'signin' do page.must_have_content 'Invalid login' end - it 'fails for missing login' do + it 'fails for missing signin' do visit '/' click_link 'Sign In' auth = {username: SecureRandom.hex, password: Faker::Internet.password} @@ -37,7 +37,7 @@ describe 'signin' do page.must_have_content 'Invalid login' end - it 'logs in with proper credentials' do + it 'signs in with proper credentials' do visit '/' click_button 'Create My Website' fill_in_valid_signup @@ -50,4 +50,18 @@ describe 'signin' do click_button 'Sign In' page.must_have_content 'Your Feed' end + + it 'signs in with email' do + visit '/' + click_button 'Create My Website' + fill_in_valid_signup + click_button 'Create Home Page' + Capybara.reset_sessions! + visit '/' + click_link 'Sign In' + fill_in 'username', with: @site[:email] + fill_in 'password', with: @site[:password] + click_button 'Sign In' + page.must_have_content 'Your Feed' + end end \ No newline at end of file diff --git a/views/_header.erb b/views/_header.erb index fd9f7039..fe6e3b2c 100644 --- a/views/_header.erb +++ b/views/_header.erb @@ -33,15 +33,35 @@ Sign In <% else %> -
- You get <%= Site::FREE_MAXIMUM_IN_MEGABYTES %> MB of free web space to make whatever you’d like! + You get <%= Site::FREE_MAXIMUM.to_space_pretty %> of free web space to make whatever you’d like!
The site you are creating will be free, forever. We will never charge you for your web site.
Neocities has to pay the bills though, and we like the idea of being able to work on the site full-time someday. So if you would like to help us reach this goal, we have created the Supporter Plan! -
Right now, the Supporter Plan is the same as the free plan, except that Supporter Plan members get 200MB of web space. You will also be listed as a supporter on our contributors page, and on your site profile page.
+Right now, the Supporter Plan is the same as the free plan, except that Supporter Plan members get <%= Site::SUPPORTER_MAXIMUM.to_space_pretty %> of web space. You will also be listed as a supporter on our contributors page, and on your site profile page.
The base plan is $12 ($1/month) billed once per year, which is the cost of a delicious Yafa Combo with a lousy tip. If you ever decide to cancel, you get to keep the extra space. Thanks for helping us run this site!