start on accounts using site context, improved settings page, clean up space representation

This commit is contained in:
Kyle Drake 2014-10-07 02:22:08 +02:00
parent 8a36f1c6f5
commit d3c1546d45
17 changed files with 243 additions and 105 deletions

49
app.rb
View file

@ -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, <b>don't forget it</b>."
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

View file

@ -1,5 +1,15 @@
class Numeric
ONE_MEGABYTE = 1048576
def roundup(nearest=10)
self % nearest == 0 ? self : self + nearest - (self % nearest)
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

View file

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

View file

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

View file

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

View file

@ -33,6 +33,22 @@
<a href="/signin" class="sign-In" title="Sign into your account">Sign In</a>
</li>
<% else %>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<%= current_site.username %> <b class="caret"></b>
</a>
<ul class="dropdown-menu pull-left">
<li class="dropdown-submenu pull-left">
<a tabindex="-1" href="#">Change Site</a>
<ul class="dropdown-menu">
<% current_site.other_sites.each do |site| %>
<li>
<a href="#"><%= site.username %></a><br>
</li>
<% end %>
</ul>
</li>
<li>
<a href="/dashboard" class="sign-In">Edit Site</a>
</li>
@ -42,6 +58,10 @@
<li>
<a href="/signout" class="sign-In">Signout</a>
</li>
</ul>
</li>
<% end %>
</ul>

View file

@ -34,7 +34,7 @@
<% if current_site.updated_at %>
<li>Last updated <%= current_site.updated_at.ago.downcase %></li>
<% end %>
<li>Using <strong><%= current_site.space_percentage_used %>% (<%= current_site.used_space_in_megabytes %>MB) of your <%= current_site.maximum_space_in_megabytes %> MB</strong>.
<li>Using <strong><%= current_site.space_percentage_used %>% (<%= current_site.total_used_space.to_space_pretty %>) of your <%= current_site.maximum_space.to_space_pretty %></strong>.
<br>
<% if !current_site.supporter? %>Need more space? <a href="/plan">Become a Supporter!</a><% end %></li>
<li><strong><%= current_site.hits.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse %></strong> hits</li>
@ -222,7 +222,7 @@
Dropzone.options.uploads = {
paramName: 'files',
maxFilesize: <%= current_site.available_space_in_megabytes %>,
maxFilesize: <%= current_site.remaining_space.to_mb %>,
clickable: false,
addRemoveLinks: false,
dictDefaultMessage: '',

View file

@ -123,7 +123,7 @@
<span class="intro-Icon"></span>
<h2 class="delta">Create your own free website</h2>
<p class="tiny">
You get <%= Site::FREE_MAXIMUM_IN_MEGABYTES %> MB of free web space to make whatever youd like!
You get <%= Site::FREE_MAXIMUM.to_space_pretty %> of free web space to make whatever youd like!
</p>
</li>
<li class="intro-Social">

View file

@ -73,17 +73,17 @@
<p><strong>The site you are creating will be free, forever.</strong> We will never charge you for your web site.</p>
<p><a href="/donate" target="_blank">Neocities has to pay the bills though</a>, 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 <strong>Supporter Plan</strong>!
<p>Right now, the <strong>Supporter Plan</strong> is the same as the free plan, except that <strong>Supporter Plan members get 200MB</strong> of web space. You will also be listed as a supporter on our contributors page, and on your site profile page.</p>
<p>Right now, the <strong>Supporter Plan</strong> is the same as the free plan, except that <strong>Supporter Plan members get <%= Site::SUPPORTER_MAXIMUM.to_space_pretty %></strong> of web space. You will also be listed as a supporter on our contributors page, and on your site profile page.</p>
<p>The base plan is $12 ($1/month) billed once per year, which is the cost of <a href="/img/yafagrillmenu.jpg" target="_blank">a delicious Yafa Combo with a lousy tip</a>. If you ever decide to cancel, you get to keep the extra space. Thanks for helping us run this site!</p>
<div>
<input type="radio" name="plan" value="free" <%= params[:plan].nil? || params[:plan] == 'free' ? 'checked' : '' %>>
<span><strong>Free Plan (<%= Site::FREE_MAXIMUM_IN_MEGABYTES %>MB)</strong></span>
<span><strong>Free Plan (<%= Site::FREE_MAXIMUM.to_space_pretty %>)</strong></span>
</div>
<a name="plan_error_link"></a>
<div>
<input id="supporter" type="radio" name="plan" value="supporter" <%= params[:plan] == 'supporter' ? 'checked' : '' %>>
<span><strong>Supporter Plan (<%= Site::SUPPORTER_MAXIMUM_IN_MEGABYTES %>MB)</strong></span>
<span><strong>Supporter Plan (<%= Site::SUPPORTER_MAXIMUM.to_space_pretty %>)</strong></span>
</div>
<div id="plan_container" style="margin-top:20px; display: none">

View file

@ -121,7 +121,7 @@
<div class="col col-50">
<h2 class="section-header">Introducing the New Neocities</h2>
<p class="intro-text">Nows a great time to join our community of over <%= @sites_count.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse %> websites!
You get <%= Site::FREE_MAXIMUM_IN_MEGABYTES %> MB of free web space to make whatever youd like. </p>
You get <%= Site::FREE_MAXIMUM.to_space_pretty %> of free web space to make whatever youd like. </p>
<ul class="intro-List">
<li class="intro-Tools">
<span class="intro-Icon"></span>

View file

@ -12,7 +12,7 @@
<div class="content single-Col misc-page">
<h3>Ending the Supporter Plan</h3>
<p>
You currently have the <strong><%= current_site.plan_name %> (<%= current_site.maximum_space_in_megabytes %>MB)</strong> - <%= @plan_name %>.
You currently have the <strong><%= current_site.plan_name %> (<%= current_site.maximum_space.to_space_pretty %>)</strong> - <%= @plan_name %>.
</p>
<p>
If you need to end the plan, you can do that here. We'll be sorry to see you go. If there's a reason you're ending that we can help with, please <a href="/contact">contact us</a> and we'll see if we can help you with your issue. Regardless, we'll let you keep your site and the extra space. We hope you'll decide to become a supporter again in the future!

View file

@ -19,7 +19,7 @@
<% if current_site && current_site.supporter? && !current_site.plan_ended %>
<p>
You currently have the <strong><%= current_site.plan_name %> (<%= current_site.maximum_space_in_megabytes %>MB)</strong>.
You currently have the <strong><%= current_site.plan_name %> (<%= current_site.maximum_space.to_space_pretty %>)</strong>.
</p>
<p>
Your support means a lot to us. On behalf of Penelope the cat and everyone at Neocities, thank you. If there's anything we can do to make your experience even better, please don't hesitate to <a href="/contact">contact us</a>.
@ -28,7 +28,7 @@
<a href="/plan/manage">Manage plan</a>
<% else %>
<% if current_site %>
<p>You currently have the <strong>Free Plan (<%= current_site.maximum_space_in_megabytes %>MB)</strong>. Need more space? Become a <strong>Neocities Supporter!</strong></p>
<p>You currently have the <strong>Free Plan (<%= current_site.maximum_space.to_space_pretty %>)</strong>. Need more space? Become a <strong>Neocities Supporter!</strong></p>
<h3>Why upgrade?</h3>
<% else %>
@ -38,7 +38,7 @@
<% end %>
<ul>
<li>
<strong>You get more space!</strong> Right now supporter plans get up to <strong><%= Site::SUPPORTER_MAXIMUM_IN_MEGABYTES %>MB</strong> (more coming soon)</strong>.
<strong>You get more space!</strong> Right now supporter plans get up to <strong><%= Site::SUPPORTER_MAXIMUM.to_space_pretty %></strong>.
</li>
<li>
<strong>It helps your site.</strong> Funding helps us make your site faster globally, and provide more features.

View file

@ -12,7 +12,7 @@
<div class="content single-Col misc-page">
<h3>Change your Supporter Plan</h3>
<p>
You currently have the <strong><%= current_site.plan_name %> (<%= current_site.maximum_space_in_megabytes %>MB)</strong> - <%= @plan_name %>.
You currently have the <strong><%= current_site.plan_name %> (<%= current_site.maximum_space.to_space_pretty %>)</strong> - <%= @plan_name %>.
</p>
<a href="/plan/end">End plan</a>

View file

@ -9,48 +9,22 @@
<article>
<section>
<div class="txt-Center">
<% if !current_site.errors.empty? %>
<div class="alert alert-block alert-error" style="margin-top: 20px">
<% current_site.errors.each do |error| %>
<p><%== error.last.first %></p>
<% end %>
</div>
<% end %>
<% if flash[:success] %>
<div class="alert alert-block alert-success" style="margin-top: 20px">
<%== flash[:success] %>
</div>
<% end %>
</div>
<!--
<ul class="nav nav-tabs">
<li class="active">
<a href="#">Plan</a>
</li>
<li>
<a href="#">Profile</a>
</li>
<li>
<a href="">Custom Domain</a>
</li>
<li>
<a href="">Password</a>
</li>
<li>
<a href="">Email</a>
</li>
<li>
<a href="">Username</a>
</li>
<li>
<a href="">18+</a>
</li>
</ul>
-->
<% if flash[:error] %>
<div class="alert alert-block alert-error" style="margin-top: 20px">
<%== flash[:error] %>
</div>
<% end %>
</div>
<div class="tabbable" style="margin-top: 20px"> <!-- Only required for left/right tabs -->
<ul class="nav nav-tabs">
<li class="active"><a href="#plan" data-toggle="tab">Plan</a></li>
<li><a href="#sites" data-toggle="tab">Sites</a></li>
<li><a href="#profile" data-toggle="tab">Profile</a></li>
<li><a href="#customdomain" data-toggle="tab">Custom Domain</a></li>
<li><a href="#password" data-toggle="tab">Password</a></li>
@ -62,6 +36,9 @@
<div class="tab-pane active" id="plan">
<%== erb :'settings/plan' %>
</div>
<div class="tab-pane" id="sites">
<%== erb :'settings/sites' %>
</div>
<div class="tab-pane" id="profile">
<%== erb :'settings/profile' %>
</div>
@ -117,3 +94,12 @@
</div>
</form>
</div>
<script>
$(document).ready(function() {
if (location.hash !== '') $('a[href="' + location.hash + '"]').tab('show');
return $('a[data-toggle="tab"]').on('shown', function(e) {
return location.hash = $(e.target).attr('href').substr(1);
});
});
</script>

View file

@ -1,11 +1,11 @@
<h2>Neocities Plan</h2>
<% if current_site.supporter? && !current_site.plan_ended %>
<p class="tiny">You currently have the <strong>Supporter Plan (<%= current_site.maximum_space_in_megabytes %>MB)</strong>. Thank you! We love you.
<p class="tiny">You currently have the <strong>Supporter Plan (<%= current_site.maximum_space.to_space_pretty %>)</strong>. Thank you! We love you.
</p>
<a class="btn-Action" href="/plan">Manage Plan</a>
<% else %>
<p class="tiny">
You currently have the <strong>Free Plan (<%= current_site.maximum_space_in_megabytes %>MB)</strong>.<br>Want to get more space and help Neocities? Become a supporter!
You currently have the <strong>Free Plan (<%= current_site.maximum_space.to_space_pretty %>)</strong>.<br>Want to get more space and help Neocities? Become a supporter!
</p>
<a class="btn-Action" href="/plan">Supporter Info</a>
<% end %>

42
views/settings/sites.erb Normal file
View file

@ -0,0 +1,42 @@
<h2>Your Sites</h2>
<% if current_site.children_dataset.count == 0 %>
<h6>No other sites are currently linked to this account.</h6>
<% else %>
<table class="table">
<tr>
<td>
<a href="//<%= current_site.host %>"><strong><%= current_site.title %></strong></a> (primary)
</td>
<td>
Manage this site
</td>
</tr>
<% current_site.children.each do |site| %>
<tr>
<td>
<a href="//<%= site.host %>" target="_blank"><%= site.title %></a>
</td>
<td>
<a href="#">Manage this site</a>
</td>
</tr>
<% end %>
</table>
<% end %>
<h3>Create New Site</h3>
<p>You can now create new sites that are linked to this account! Sites will share the free space you have available. You have <strong><%= Site::CHILD_SITES_MAX - current_site.children_dataset.count %></strong> new sites remaining.</p>
<form action="/site/create_child" method="POST">
<%== csrf_token_input_html %>
<p>Site Name:</p>
<input name="username" type="text">
<div>
<input class="btn-Action" type="submit" value="Create New Site">
</div>
</form>

View file

@ -1,6 +1,6 @@
Hi there!
We just wanted to email you to confirm your supporter upgrade! You are on the <%= plan_name %> support tier. You now have <%= Site::SUPPORTER_MAXIMUM_IN_MEGABYTES %> MB of space (and we're likely to upgrade this even more soon). We're looking forward to seeing the awesome things you will do with it.
We just wanted to email you to confirm your supporter upgrade! You are on the <%= plan_name %> support tier. You now have <%= Site::SUPPORTER_MAXIMUM.to_space_pretty %> of space (and we're likely to upgrade this even more soon). We're looking forward to seeing the awesome things you will do with it.
Thank you very, very much for supporting Neocities. It means a lot to us. It also helps us keep the free tier for others, so your support helps everyone. We'll try our hardest to keep improving the site and stick to our core values (NO MARKETING OR ADVERTISING, EVER).