merge commit

This commit is contained in:
Victoria Wang 2014-10-13 15:18:00 -07:00
commit ed660595df
30 changed files with 820 additions and 551 deletions

520
app.rb
View file

@ -125,6 +125,23 @@ post '/site/:username/set_editor_theme' do
'ok'
end
post '/settings/create_child' do
require_login
site = Site.new
site.parent_site_id = parent_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
@ -254,68 +271,74 @@ post '/plan/create' do
DB.transaction do
customer = Stripe::Customer.create(
card: params[:stripe_token],
description: current_site.username,
description: "#{parent_site.username} - #{parent_site.id}",
email: current_site.email,
plan: params[:selected_plan]
)
current_site.update stripe_customer_id: customer.id, plan_ended: false
parent_site.update stripe_customer_id: customer.id, plan_ended: false
plan_name = customer.subscriptions.first['plan']['name']
EmailWorker.perform_async({
from: 'web@neocities.org',
reply_to: 'contact@neocities.org',
to: current_site.email,
subject: "[Neocities] You've become a supporter!",
body: Tilt.new('./views/templates/email_subscription.erb', pretty: true).render(self, plan_name: plan_name)
})
if current_site.email || parent_site.email
EmailWorker.perform_async({
from: 'web@neocities.org',
reply_to: 'contact@neocities.org',
to: current_site.email || parent_site.email,
subject: "[Neocities] You've become a supporter!",
body: Tilt.new('./views/templates/email_subscription.erb', pretty: true).render(self, plan_name: plan_name)
})
end
end
redirect '/plan'
end
def get_plan_name(customer_id)
subscriptions = Stripe::Customer.retrieve(current_site.stripe_customer_id).subscriptions.all
subscriptions = Stripe::Customer.retrieve(parent_site.stripe_customer_id).subscriptions.all
@plan_name = subscriptions.first.plan.name
end
def require_active_subscription
redirect '/plan' unless parent_site.supporter? && !parent_site.plan_ended
end
get '/plan/manage' do
require_login
redirect '/plan' unless current_site.supporter? && !current_site.plan_ended
require_active_subscription
@title = 'Manage Plan'
@plan_name = get_plan_name current_site.stripe_customer_id
@plan_name = get_plan_name parent_site.stripe_customer_id
erb :'plan/manage'
end
get '/plan/end' do
require_login
redirect '/plan' unless current_site.supporter? && !current_site.plan_ended
require_active_subscription
@title = 'End Plan'
@plan_name = get_plan_name current_site.stripe_customer_id
@plan_name = get_plan_name parent_site.stripe_customer_id
erb :'plan/end'
end
post '/plan/end' do
require_login
redirect '/plan' unless current_site.supporter? && !current_site.plan_ended
require_active_subscription
recaptcha_is_valid = ENV['RACK_ENV'] == 'test' || recaptcha_valid?
if !recaptcha_is_valid
@error = 'Recaptcha was filled out incorrectly, please try re-entering.'
@plan_name = get_plan_name current_site.stripe_customer_id
@plan_name = get_plan_name parent_site.stripe_customer_id
halt erb :'plan/end'
end
customer = Stripe::Customer.retrieve current_site.stripe_customer_id
customer = Stripe::Customer.retrieve parent_site.stripe_customer_id
subscriptions = customer.subscriptions.all
DB.transaction do
subscriptions.each do |subscription|
customer.subscriptions.retrieve(subscription.id).delete
end
current_site.update plan_ended: true
parent_site.update plan_ended: true
end
redirect '/plan'
@ -531,43 +554,59 @@ get '/dashboard' do
erb :'dashboard'
end
get '/signin' do
dashboard_if_signed_in
erb :'signin'
get '/settings/?' do
require_login
@site = parent_site
erb :'settings/account'
end
get '/settings' do
require_login
erb :'settings'
def require_ownership_for_settings
@site = Site[username: params[:username]]
not_found if @site.nil?
unless @site.owned_by? parent_site
flash[:error] = 'Cannot edit this site, you do not have permission.'
redirect request.referrer
end
end
post '/settings/profile' do
get '/settings/:username/?' do
require_login
current_site.update(
require_ownership_for_settings
erb :'settings/site'
end
post '/settings/:username/profile' do
require_login
require_ownership_for_settings
@site.update(
profile_comments_enabled: params[:site][:profile_comments_enabled]
)
flash[:success] = 'Profile settings changed.'
redirect '/settings'
redirect "/settings/#{@site.username}#profile"
end
post '/settings/ssl' do
post '/settings/:username/ssl' do
require_login
require_ownership_for_settings
unless params[:key] && params[:cert]
flash[:error] = 'SSL key and certificate are required.'
redirect '/custom_domain'
redirect "/settings/#{@site.username}#custom_domain"
end
begin
key = OpenSSL::PKey::RSA.new params[:key][:tempfile].read, ''
rescue => e
flash[:error] = 'Could not process SSL key, file may be incorrect, damaged, or passworded (you need to remove the password).'
redirect '/custom_domain'
redirect "/settings/#{@site.username}#custom_domain"
end
if !key.private?
flash[:error] = 'SSL Key file does not have private key data.'
redirect '/custom_domain'
redirect "/settings/#{@site.username}#custom_domain"
end
certs_string = params[:cert][:tempfile].read
@ -576,7 +615,7 @@ post '/settings/ssl' do
if cert_array.empty?
flash[:error] = 'Cert file does not contain any certificates.'
redirect '/custom_domain'
redirect "/settings/#{@site.username}#custom_domain"
end
cert_valid_for_domain = false
@ -586,21 +625,21 @@ post '/settings/ssl' do
cert = OpenSSL::X509::Certificate.new cert_string
rescue => e
flash[:error] = 'Could not process SSL certificate, file may be incorrect or damaged.'
redirect '/custom_domain'
redirect "/settings/#{@site.username}#custom_domain"
end
if cert.not_after < Time.now
flash[:error] = 'SSL Certificate has expired, please create a new one.'
redirect '/custom_domain'
redirect "/settings/#{@site.username}#custom_domain"
end
cert_cn = cert.subject.to_a.select {|a| a.first == 'CN'}.flatten[1]
cert_valid_for_domain = true if cert_cn && cert_cn.match(current_site.domain)
cert_valid_for_domain = true if cert_cn && cert_cn.match(@site.domain)
end
unless cert_valid_for_domain
flash[:error] = "Your certificate CN (common name) does not match your domain: #{current_site.domain}"
redirect '/custom_domain'
flash[:error] = "Your certificate CN (common name) does not match your domain: #{@site.domain}"
redirect "/settings/#{@site.username}#custom_domain"
end
# Everything else was worse.
@ -623,7 +662,7 @@ post '/settings/ssl' do
access_log off;
server {
listen 60000 ssl;
server_name #{current_site.domain} *.#{current_site.domain};
server_name #{@site.domain} *.#{@site.domain};
ssl_certificate #{crtfile.path};
ssl_certificate_key #{keyfile.path};
}
@ -641,21 +680,209 @@ post '/settings/ssl' do
output = line.run path: nginx_testfile.path
rescue Cocaine::ExitStatusError => e
flash[:error] = "There is something wrong with your certificate, please check with your issuing CA."
redirect '/custom_domain'
redirect "/settings/#{@site.username}#custom_domain"
end
end
current_site.update ssl_key: key.to_pem, ssl_cert: cert_array.join
@site.update ssl_key: key.to_pem, ssl_cert: cert_array.join
flash[:success] = 'Updated SSL key/certificate.'
redirect '/custom_domain'
redirect "/settings/#{@site.username}#custom_domain"
end
post '/settings/:username/change_name' do
require_login
require_ownership_for_settings
old_username = @site.username
if params[:name] == nil || params[:name] == ''
flash[:error] = 'Name cannot be blank.'
redirect "/settings/#{@site.username}#username"
end
if old_username == params[:name]
flash[:error] = 'You already have this name.'
redirect "/settings/#{@site.username}#username"
end
old_host = @site.host
old_file_paths = @site.file_list.collect {|f| f[:path]}
@site.username = params[:name]
if @site.valid?
DB.transaction {
@site.save_changes
@site.move_files_from old_username
}
old_file_paths.each do |file_path|
@site.purge_cache file_path
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/#{@site.username}#username"
else
flash[:error] = @site.errors.first.last.first
redirect "/settings/#{old_username}#username"
end
end
post '/settings/:username/change_nsfw' do
require_login
require_ownership_for_settings
@site.update is_nsfw: params[:is_nsfw]
flash[:success] = @site.is_nsfw ? 'Marked 18+' : 'Unmarked 18+'
redirect "/settings/#{@site.username}#nsfw"
end
post '/settings/:username/custom_domain' do
require_login
require_ownership_for_settings
@site.domain = params[:domain]
if @site.valid?
@site.save_changes
flash[:success] = 'The domain has been successfully updated.'
redirect "/settings/#{@site.username}#custom_domain"
else
flash[:error] = @site.errors.first.last.first
redirect "/settings/#{@site.username}#custom_domain"
end
end
post '/settings/change_password' do
require_login
if !Site.valid_login?(parent_site.username, params[:current_password])
flash[:error] = 'Your provided password does not match the current one.'
redirect "/settings#password"
end
parent_site.password = params[:new_password]
parent_site.valid?
if params[:new_password] != params[:new_password_confirm]
parent_site.errors.add :password, 'New passwords do not match.'
end
if parent_site.errors.empty?
parent_site.save_changes
flash[:success] = 'Successfully changed password.'
redirect "/settings#password"
else
flash[:error] = current_site.errors.first.last.first
redirect '/settings#password'
end
end
post '/settings/change_email' do
require_login
if params[:email] == parent_site.email
flash[:error] = 'You are already using this email address for this account.'
redirect '/settings#email'
end
parent_site.email = params[:email]
parent_site.email_confirmation_token = SecureRandom.hex 3
parent_site.email_confirmed = false
if parent_site.valid?
parent_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#email'
end
flash[:error] = parent_site.errors.first.last.first
redirect '/settings#email'
end
get '/password_reset' do
erb :'password_reset'
end
post '/send_password_reset' do
sites = Site.filter(email: params[:email]).all
if sites.length > 0
token = SecureRandom.uuid.gsub('-', '')
sites.each do |site|
site.update password_reset_token: token
end
body = <<-EOT
Hello! This is the Neocities cat, and I have received a password reset request for your e-mail address. Purrrr.
Go to this URL to reset your password: http://neocities.org/password_reset_confirm?token=#{token}
After clicking on this link, your password for all the sites registered to this email address will be changed to this token.
Token: #{token}
If you didn't request this reset, you can ignore it. Or hide under a bed. Or take a nap. Your call.
Meow,
the Neocities Cat
EOT
body.strip!
EmailWorker.perform_async({
from: 'web@neocities.org',
to: params[:email],
subject: '[Neocities] Password Reset',
body: body
})
end
flash[:success] = 'If your email was valid (and used by a site), the Neocities Cat will send an e-mail to your account with password reset instructions.'
redirect '/'
end
get '/password_reset_confirm' do
if params[:token].nil? || params[:token].empty?
flash[:error] = 'Could not find a site with this token.'
redirect '/'
end
reset_site = Site[password_reset_token: params[:token]]
if reset_site.nil?
flash[:error] = 'Could not find a site with this token.'
redirect '/'
end
sites = Site.filter(email: reset_site.email).all
if sites.length > 0
sites.each do |site|
site.password = reset_site.password_reset_token
site.save_changes
end
flash[:success] = 'Your password for all sites with your email address has been changed to the token sent in your e-mail. Please login and change your password as soon as possible.'
else
flash[:error] = 'Could not find a site with this token.'
end
redirect '/'
end
get '/signin/?' do
dashboard_if_signed_in
erb :'signin'
end
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.'
@ -678,6 +905,21 @@ get '/signout' do
redirect '/'
end
get '/signin/:username' do
require_login
@site = Site[username: params[:username]]
not_found if @site.nil?
if @site.owned_by? current_site
session[:id] = @site.id
redirect request.referrer
end
flash[:error] = 'You do not have permission to switch to this site.'
redirect request.referrer
end
get '/about' do
erb :'about'
end
@ -687,96 +929,6 @@ get '/site_files/new_page' do
erb :'site_files/new_page'
end
post '/change_password' do
require_login
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')
end
current_site.password = params[:new_password]
current_site.valid?
if params[:new_password] != params[:new_password_confirm]
current_site.errors.add :password, 'New passwords do not match.'
end
if current_site.errors.empty?
current_site.save_changes
flash[:success] = 'Successfully changed password.'
redirect '/settings'
else
halt erb(:'settings')
end
end
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)
end
current_site.email = params[:email]
current_site.email_confirmation_token = SecureRandom.hex 3
current_site.email_confirmed = false
if current_site.valid?
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'
end
current_site.reload
erb :settings
end
post '/change_name' do
require_login
old_username = current_site.username
if params[:name] == nil || params[:name] == ''
flash[:error] = 'Name cannot be blank.'
redirect '/settings'
end
if old_username == params[:name]
flash[:error] = 'You already have this name.'
redirect '/settings'
end
old_host = current_site.host
old_file_paths = current_site.file_list.collect {|f| f[:path]}
current_site.username = params[:name]
if current_site.valid?
DB.transaction {
current_site.save_changes
current_site.move_files_from old_username
}
old_file_paths.each do |file_path|
current_site.purge_cache file_path
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'
else
halt erb(:'settings')
end
end
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'
end
post '/site_files/create_page' do
require_login
@errors = []
@ -1008,95 +1160,6 @@ post '/admin/mark_nsfw' do
redirect '/admin'
end
get '/password_reset' do
erb :'password_reset'
end
post '/send_password_reset' do
sites = Site.filter(email: params[:email]).all
if sites.length > 0
token = SecureRandom.uuid.gsub('-', '')
sites.each do |site|
site.update password_reset_token: token
end
body = <<-EOT
Hello! This is the Neocities cat, and I have received a password reset request for your e-mail address. Purrrr.
Go to this URL to reset your password: http://neocities.org/password_reset_confirm?token=#{token}
After clicking on this link, your password for all the sites registered to this email address will be changed to this token.
Token: #{token}
If you didn't request this reset, you can ignore it. Or hide under a bed. Or take a nap. Your call.
Meow,
the Neocities Cat
EOT
body.strip!
EmailWorker.perform_async({
from: 'web@neocities.org',
to: params[:email],
subject: '[Neocities] Password Reset',
body: body
})
end
flash[:success] = 'If your email was valid (and used by a site), the Neocities Cat will send an e-mail to your account with password reset instructions.'
redirect '/'
end
get '/password_reset_confirm' do
if params[:token].nil? || params[:token].empty?
flash[:error] = 'Could not find a site with this token.'
redirect '/'
end
reset_site = Site[password_reset_token: params[:token]]
if reset_site.nil?
flash[:error] = 'Could not find a site with this token.'
redirect '/'
end
sites = Site.filter(email: reset_site.email).all
if sites.length > 0
sites.each do |site|
site.password = reset_site.password_reset_token
site.save_changes
end
flash[:success] = 'Your password for all sites with your email address has been changed to the token sent in your e-mail. Please login and change your password as soon as possible.'
else
flash[:error] = 'Could not find a site with this token.'
end
redirect '/'
end
get '/custom_domain' do
require_login
erb :custom_domain
end
post '/custom_domain' do
require_login
current_site.domain = params[:domain]
if current_site.valid?
current_site.save_changes
flash[:success] = 'The domain has been successfully updated.'
redirect '/custom_domain'
else
erb :custom_domain
end
end
get '/contact' do
erb :'contact'
end
@ -1361,14 +1424,6 @@ post '/site/:username/block' do |username|
end
end
post '/site/delete' do
require_login
if current_site.username != params[:username]
current_site.errors.add :username, 'Could not delete site, site name did not match.'
halt erb(:settings)
end
end
def require_admin
redirect '/' unless signed_in? && current_site.is_admin
end
@ -1401,7 +1456,12 @@ end
def current_site
return nil if session[:id].nil?
@site ||= Site[id: session[:id]]
@_site ||= Site[id: session[:id]]
end
def parent_site
return nil if current_site.nil?
current_site.parent? ? current_site : current_site.parent
end
def require_unbanned_ip

View file

@ -1,5 +1,17 @@
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
space = (self.to_f / ONE_MEGABYTE).round(2)
space = space.to_i if space.denominator == 1
"#{space} 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 = 100
PLAN_FEATURES[:catbus] = PLAN_FEATURES[:fatcat].merge(
name: 'Cat Bus',
@ -147,9 +145,42 @@ 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_dataset
Site.where(Sequel.|({id: owner.id}, {parent_site_id: owner.id})).order(:parent_site_id.desc, :username)
end
def account_sites
account_sites_dataset.all
end
def other_sites_dataset
account_sites_dataset.exclude(id: self.id)
end
def other_sites
account_sites_dataset.exclude(id: self.id).all
end
def account_sites_events_dataset
ids = account_sites_dataset.select(:id).all.collect {|s| s.id}
Event.where(id: ids)
end
def owner
parent? ? self : parent
end
def owned_by?(site)
!account_sites_dataset.select(:id).where(id: site.id).first.nil?
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 +192,16 @@ 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
return nil if site.nil? || site.is_banned || site.owner.is_banned
site
end
end
def self.banned_ip?(ip)
@ -204,7 +245,14 @@ class Site < Sequel::Model
end
def valid_password?(plaintext)
BCrypt::Password.new(values[:password]) == plaintext
valid = BCrypt::Password.new(owner.values[:password]) == plaintext
if !valid?
return false if values[:password].nil?
valid = BCrypt::Password.new(values[:password]) == plaintext
end
valid
end
def password=(plaintext)
@ -297,6 +345,12 @@ class Site < Sequel::Model
end
end
def ban_all_sites_on_account!
DB.transaction {
account_sites.all {|site| site.ban! }
}
end
=begin
def follows_dataset
super.where(Sequel.~(site_id: blocking_site_ids))
@ -315,15 +369,21 @@ class Site < Sequel::Model
=end
def commenting_allowed?
return true if commenting_allowed
return true if owner.commenting_allowed
if events_dataset.exclude(site_change_id: nil).count >= COMMENTING_ALLOWED_UPDATED_COUNT &&
created_at < Time.now - 604800
if owner.supporter?
set commenting_allowed: true
save_changes validate: false
return true
end
if account_sites_events_dataset.exclude(site_change_id: nil).count >= COMMENTING_ALLOWED_UPDATED_COUNT &&
created_at < Time.now - 604800
owner.set commenting_allowed: true
owner.save_changes validate: false
return true
end
false
end
@ -554,6 +614,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 +640,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 +650,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 +668,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 +686,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? && account_sites_dataset.count >= CHILD_SITES_MAX
errors.add :child_site_id, "Cannot add child site, exceeds #{CHILD_SITES_MAX} limit."
end
end
if @new_tags_string
@ -715,49 +783,43 @@ 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.
def supporter?
!values[:stripe_customer_id].nil?
!owner.values[:stripe_customer_id].nil?
end
# This will return false if they have ended their plan.
def ended_supporter?
values[:plan_ended]
owner.values[:plan_ended]
end
def plan_name

View file

@ -1051,4 +1051,8 @@ a.tag:hover {
.interior .header-Outro.with-columns .col.filter {
padding-top: 0px;
padding-bottom: 4px;
}
.dropdown-submenu .dropdown-menu {
width: 1px;
}

View file

@ -0,0 +1,86 @@
require_relative '../environment.rb'
describe 'site/settings' do
describe 'email' do
include Capybara::DSL
before do
EmailWorker.jobs.clear
@email = "#{SecureRandom.uuid.gsub('-', '')}@example.com"
@site = Fabricate :site, email: @email
page.set_rack_session id: @site.id
visit '/settings'
end
it 'should change email' do
@new_email = "#{SecureRandom.uuid.gsub('-', '')}@example.com"
fill_in 'email', with: @new_email
click_button 'Change Email'
page.must_have_content /successfully changed email/i
@site.reload
@site.email.must_equal @new_email
EmailWorker.jobs.length.must_equal 1
args = EmailWorker.jobs.first['args'].first
args['to'].must_equal @new_email
args['subject'].must_match /confirm your email address/i
args['body'].must_match /hello #{@site.username}/i
args['body'].must_match /#{@site.email_confirmation_token}/
end
it 'should fail for invalid email address' do
@new_email = SecureRandom.uuid.gsub '-', ''
fill_in 'email', with: @new_email
click_button 'Change Email'
page.must_have_content /a valid email address is required/i
@site.reload
@site.email.wont_equal @new_email
EmailWorker.jobs.empty?.must_equal true
end
it 'should fail for existing email' do
@existing_email = "#{SecureRandom.uuid.gsub('-', '')}@example.com"
@existing_site = Fabricate :site, email: @existing_email
fill_in 'email', with: @existing_email
click_button 'Change Email'
page.must_have_content /this email address already exists on neocities/i
@site.reload
@site.email.wont_equal @new_email
EmailWorker.jobs.empty?.must_equal true
end
end
describe 'change password' do
include Capybara::DSL
before do
@site = Fabricate :site, password: 'derpie'
page.set_rack_session id: @site.id
visit '/settings'
end
it 'should change correctly' do
fill_in 'current_password', with: 'derpie'
fill_in 'new_password', with: 'derpie2'
fill_in 'new_password_confirm', with: 'derpie2'
click_button 'Change Password'
page.must_have_content /successfully changed password/i
@site.reload
@site.valid_password?('derpie').must_equal false
@site.valid_password?('derpie2').must_equal true
end
it 'should not change for invalid current password' do
fill_in 'current_password', with: 'dademurphy'
fill_in 'new_password', with: 'derpie2'
fill_in 'new_password_confirm', with: 'derpie2'
click_button 'Change Password'
page.must_have_content /provided password does not match the current one/i
@site.reload
@site.valid_password?('derpie').must_equal true
@site.valid_password?('derpie2').must_equal false
end
end
end

View file

@ -1,4 +1,4 @@
require_relative './environment.rb'
require_relative '../environment.rb'
def generate_ssl_certs(opts={})
# https://github.com/kyledrake/ruby-openssl-cheat-sheet/blob/master/certificate_authority.rb
@ -80,6 +80,29 @@ def generate_ssl_certs(opts={})
end
describe 'site/settings' do
describe 'permissions' do
include Capybara::DSL
before do
@parent_site = Fabricate :site
@child_site = Fabricate :site, parent_site_id: @parent_site.id
@other_site = Fabricate :site
end
it 'fails without permissions' do
page.set_rack_session id: @other_site.id
visit "/settings/#{@parent_site.username}"
page.current_path.must_equal '/' # This could be better
end
it 'allows child site editing from parent' do
page.set_rack_session id: @parent_site.id
visit "/settings/#{@child_site.username}"
page.current_path.must_equal "/settings/#{@child_site.username}"
end
end
describe 'ssl' do
include Capybara::DSL
@ -92,13 +115,13 @@ describe 'site/settings' do
it 'fails without domain set' do
@site = Fabricate :site
page.set_rack_session id: @site.id
visit '/custom_domain'
visit "/settings/#{@site.username}#custom_domain"
page.must_have_content /Cannot upload SSL certificate until domain is added/i
end
it 'fails with expired key' do
@ssl = generate_ssl_certs domain: @domain, expired: true
visit '/custom_domain'
visit "/settings/#{@site.username}#custom_domain"
attach_file 'key', @ssl[:key_path]
attach_file 'cert', @ssl[:combined_cert_path]
click_button 'Upload SSL Key and Certificate'
@ -107,14 +130,14 @@ describe 'site/settings' do
it 'works with valid key and unified cert' do
@ssl = generate_ssl_certs domain: @domain
visit '/custom_domain'
visit "/settings/#{@site.username}#custom_domain"
key = File.read @ssl[:key_path]
combined_cert = File.read @ssl[:combined_cert_path]
page.must_have_content /status: inactive/i
attach_file 'key', @ssl[:key_path]
attach_file 'cert', @ssl[:combined_cert_path]
click_button 'Upload SSL Key and Certificate'
page.current_path.must_equal '/custom_domain'
page.current_path.must_equal "/settings/#{@site.username}"
page.must_have_content /Updated SSL/
page.must_have_content /status: installed/i
@site.reload
@ -123,9 +146,9 @@ describe 'site/settings' do
end
it 'fails with no uploads' do
visit '/custom_domain'
visit "/settings/#{@site.username}#custom_domain"
click_button 'Upload SSL Key and Certificate'
page.current_path.must_equal '/custom_domain'
page.current_path.must_equal "/settings/#{@site.username}"
page.must_have_content /ssl key.+certificate.+required/i
@site.reload
@site.ssl_key.must_equal nil
@ -134,42 +157,42 @@ describe 'site/settings' do
it 'fails gracefully with encrypted key' do
@ssl = generate_ssl_certs domain: @domain
visit '/custom_domain'
visit "/settings/#{@site.username}#custom_domain"
attach_file 'key', './tests/files/ssl/derpie.com-encrypted.key'
attach_file 'cert', @ssl[:cert_path]
click_button 'Upload SSL Key and Certificate'
page.current_path.must_equal '/custom_domain'
page.current_path.must_equal "/settings/#{@site.username}"
page.must_have_content /could not process ssl key/i
end
it 'fails with junk key' do
@ssl = generate_ssl_certs domain: @domain
visit '/custom_domain'
visit "/settings/#{@site.username}#custom_domain"
attach_file 'key', './tests/files/index.html'
attach_file 'cert', @ssl[:cert_path]
click_button 'Upload SSL Key and Certificate'
page.current_path.must_equal '/custom_domain'
page.current_path.must_equal "/settings/#{@site.username}"
page.must_have_content /could not process ssl key/i
end
it 'fails with junk cert' do
@ssl = generate_ssl_certs domain: @domain
visit '/custom_domain'
visit "/settings/#{@site.username}#custom_domain"
attach_file 'key', @ssl[:key_path]
attach_file 'cert', './tests/files/index.html'
click_button 'Upload SSL Key and Certificate'
page.current_path.must_equal '/custom_domain'
page.current_path.must_equal "/settings/#{@site.username}"
page.must_have_content /could not process ssl certificate/i
end
if ENV['TRAVIS'] != 'true'
it 'fails with bad cert chain' do
@ssl = generate_ssl_certs domain: @domain
visit '/custom_domain'
visit "/settings/#{@site.username}#custom_domain"
attach_file 'key', @ssl[:key_path]
attach_file 'cert', @ssl[:bad_combined_cert_path]
click_button 'Upload SSL Key and Certificate'
page.current_path.must_equal '/custom_domain'
page.current_path.must_equal "/settings/#{@site.username}"
page.must_have_content /there is something wrong with your certificate/i
end
end
@ -200,7 +223,7 @@ describe 'site/settings' do
click_button 'Create My Website'
fill_in_valid
click_button 'Create Home Page'
visit '/settings'
visit "/settings/#{@site[:username]}#username"
fill_in 'name', with: ''
click_button 'Change Name'
fill_in 'name', with: '../hack'
@ -215,87 +238,4 @@ describe 'site/settings' do
Site[username: ''].must_equal nil
end
end
describe 'email' do
include Capybara::DSL
before do
EmailWorker.jobs.clear
@email = "#{SecureRandom.uuid.gsub('-', '')}@example.com"
@site = Fabricate :site, email: @email
page.set_rack_session id: @site.id
visit '/settings'
end
it 'should change email' do
@new_email = "#{SecureRandom.uuid.gsub('-', '')}@example.com"
fill_in 'email', with: @new_email
click_button 'Change Email'
page.must_have_content /successfully changed email/i
@site.reload
@site.email.must_equal @new_email
EmailWorker.jobs.length.must_equal 1
args = EmailWorker.jobs.first['args'].first
args['to'].must_equal @new_email
args['subject'].must_match /confirm your email address/i
args['body'].must_match /hello #{@site.username}/i
args['body'].must_match /#{@site.email_confirmation_token}/
end
it 'should fail for invalid email address' do
@new_email = SecureRandom.uuid.gsub '-', ''
fill_in 'email', with: @new_email
click_button 'Change Email'
page.must_have_content /a valid email address is required/i
@site.reload
@site.email.wont_equal @new_email
EmailWorker.jobs.empty?.must_equal true
end
it 'should fail for existing email' do
@existing_email = "#{SecureRandom.uuid.gsub('-', '')}@example.com"
@existing_site = Fabricate :site, email: @existing_email
fill_in 'email', with: @existing_email
click_button 'Change Email'
page.must_have_content /this email address already exists on neocities/i
@site.reload
@site.email.wont_equal @new_email
EmailWorker.jobs.empty?.must_equal true
end
end
describe 'change password' do
include Capybara::DSL
before do
@site = Fabricate :site, password: 'derpie'
page.set_rack_session id: @site.id
visit '/settings'
end
it 'should change correctly' do
fill_in 'current_password', with: 'derpie'
fill_in 'new_password', with: 'derpie2'
fill_in 'new_password_confirm', with: 'derpie2'
click_button 'Change Password'
page.must_have_content /successfully changed password/i
@site.reload
@site.valid_password?('derpie').must_equal false
@site.valid_password?('derpie2').must_equal true
end
it 'should not change for invalid current password' do
fill_in 'current_password', with: 'dademurphy'
fill_in 'new_password', with: 'derpie2'
fill_in 'new_password_confirm', with: 'derpie2'
click_button 'Change Password'
page.must_have_content /provided password does not match the current one/i
@site.reload
@site.valid_password?('derpie').must_equal true
@site.valid_password?('derpie2').must_equal false
end
end
end

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

@ -36,25 +36,39 @@
<li class="dropdown">
<a href="#" data-toggle="dropdown" class="dropdown-toggle"><%= current_site.username %> <span class="info"><span class="notification-value">3</span><i class="fa fa-caret-down"></i></span></a>
<ul class="dropdown-menu">
<li><a href="/site/<%= current_site.username %>">Profile</a></li>
<li><a href="/?activity=mine">Activity <span class="notification-value">3</span></a></li>
<li class="divider"></li>
<li><a href="/dashboard">Edit Site</a></li>
<li><a href="//<%= current_site.host %>" target="_blank">View Site</a></li>
<li class="divider"></li>
<li><a href="/settings">Settings</a></li>
<li><a href="/signout">Sign Out</a></li>
<li><a href="/site/<%= current_site.username %>">Profile</a></li>
<li><a href="/?activity=mine">Activity <span class="notification-value">3</span></a></li>
<li class="divider"></li>
<li><a href="/dashboard">Edit Site</a></li>
<li><a href="//<%= current_site.host %>" target="_blank">View Site</a></li>
<li class="divider"></li>
<li><a href="/settings">Settings</a></li>
<% if current_site.other_sites_dataset.count > 0 %>
<li class="dropdown-submenu pull-right">
<a tabindex="-1" href="#" onclick="return false">Switch Site</a>
<ul class="dropdown-menu">
<% current_site.other_sites_dataset.select(:username).all.each do |site| %>
<li>
<a href="/signin/<%= site.username %>"><%= site.username %></a>
</li>
<% end %>
</ul>
</li>
<% end %>
<li><a href="/signout">Sign Out</a></li>
</ul>
</li>
<% end %>
</ul>
</nav>
<h1 class="logo int-Logo">
<a href="/" title="back to home">
<span class="hidden">Neocities.org</span>
<img src="/img/cat.png" alt="Neocities.org" />
</a>
</h1>
</header>
<h1 class="logo int-Logo">
<a href="/" title="back to home">
<span class="hidden">Neocities.org</span>
<img src="/img/cat.png" alt="Neocities.org" />
</a>
</h1>
</header>

View file

@ -17,7 +17,7 @@
<div class="row">
<div class="col col-50">
<h2>Ban User</h2>
<h2>Ban Site</h2>
<form action="/admin/banhammer" method="POST">
<%== csrf_token_input_html %>
<p>Site Name:</p>

View file

@ -1,80 +0,0 @@
<div class="header-Outro">
<div class="row content single-Col">
<h1>Custom Domain</h1>
<h3 class="subtitle">Add your own domain name to your Neocities site!</h3>
</div>
</div>
<div class="content single-Col misc-page">
<h3></h3>
<article>
<% if flash.keys.length > 0 %>
<div class="alert alert-block alert-success">
<% flash.keys.each do |key| %>
<%== flash[key] %>
<% end %>
</div>
<% end %>
<p>
Adding a custom domain allows you to have a domain name attached to your web site. So if you had a domain like <strong>catsknitting.com</strong>, you could have it point to your Neocities site!
</p>
<p>
You will have to purchase a domain name from a registrar like <a href="https://www.namecheap.com/?aff=53678" target="_blank">Namecheap</a>, and then add an A record to point your domain (catsknitting.com) to the following IP address:
</p>
<p><code>198.27.81.179</code></p>
<p>
If you want to add a <strong>www</strong> subdomain, or use a wildcard that will answer to everything (<strong>*</strong>), you will have to make a CNAME pointing to <strong>catsknitting.com</strong> for <strong>www</strong> and/or <strong>*</strong>.
</p>
<p>
After that, you can add the domain to the box below (just the <strong>catsknitting.com</strong>, don't add any subdomains), and your domain should come online within 5 minutes:
</p>
<form method="POST" action="/custom_domain">
<%== csrf_token_input_html %>
<input name="domain" type="text" placeholder="catsknitting.com" value="<%= current_site.domain %>">
<br>
<input class="btn-Action" type="submit" value="Update Domain">
</form>
</article>
<article>
<h2>Add SSL Certificate</h2>
<p>
This allows you to add an SSL key and certificate for your domain, enabling encryption for your site (https). It can take up to 5-30 minutes for the changes to go live, so please be patient. All files must be in PEM format. If your certificate is not bundled with the root and intermediate certificates, ask your certificate provider for help on how to do that.
</p>
<% if current_site.domain.nil? || current_site.domain.empty? %>
<p><strong>Cannot upload SSL certificate until domain is added.</strong></p>
<% else %>
<form method="POST" action="/settings/ssl" enctype="multipart/form-data">
<%== csrf_token_input_html %>
<p>
<strong>
Status: <%= current_site.ssl_installed? ? 'Installed' : 'Inactive' %>
</strong>
</p>
<p>
SSL Key (yourdomain.com.key):
<input name="key" type="file">
</p>
<p>
Bundled Certificates (yourdomain.com-bundle.crt):
<input name="cert" type="file">
</p>
<input class="btn-Action" type="submit" value="Upload SSL Key and Certificate">
</form>
<% end %>
</article>
</div>

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

@ -122,7 +122,8 @@
<h2 class="section-header">Introducing the New Neocities</h2>
<p class="intro-text">Nows a great time to join our community of over
<a href="/browse"><%= @sites_count.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse %> websites</a>!
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-Social">
<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><%= parent_site.plan_name %> (<%= parent_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

@ -17,9 +17,9 @@
<div class="content single-Col misc-page">
<h3></h3>
<% if current_site && current_site.supporter? && !current_site.plan_ended %>
<% if parent_site && parent_site.supporter? && !parent_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><%= parent_site.plan_name %> (<%= parent_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>.
@ -27,8 +27,8 @@
<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>
<% if parent_site %>
<p>You currently have the <strong>Free Plan (<%= parent_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><%= parent_site.plan_name %> (<%= parent_site.maximum_space.to_space_pretty %>)</strong> - <%= @plan_name %>.
</p>
<a href="/plan/end">End plan</a>

View file

@ -0,0 +1,89 @@
<div class="header-Outro">
<div class="row content single-Col">
<h1>Account Settings</h1>
<h3 class="subtitle">Manage the account for your sites</h3>
</div>
</div>
<div class="content single-Col misc-page txt-Center">
<article>
<section>
<div class="txt-Center">
<% if flash[:success] %>
<div class="alert alert-block alert-success" style="margin-top: 20px">
<%== flash[:success] %>
</div>
<% end %>
<% 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="#password" data-toggle="tab">Password</a></li>
<li><a href="#email" data-toggle="tab">Email</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="plan">
<%== erb :'settings/account/plan' %>
</div>
<div class="tab-pane" id="sites">
<%== erb :'settings/account/sites' %>
</div>
<div class="tab-pane" id="password">
<%== erb :'settings/account/password' %>
</div>
<div class="tab-pane" id="email">
<%== erb :'settings/account/email' %>
</div>
</div>
</div>
<!--
<h2>Delete Site</h2>
<p class="tiny">
If you want to delete your account, you can do that here. We're sorry to see you go, but we understand if Neocities isn't right for you. If there's any specific reason you're leaving, it would be great if you <a href="/contact">let us know</a> so we can try to make your experience better in the future.
</p>
<div>
<a href="#deleteSite" data-toggle="modal" class="btn">Delete Site</a>
</div>
-->
</section>
</article>
</div>
<div class="modal hide fade" id="deleteSite" tabindex="-1" role="dialog" aria-labelledby="deleteSiteLabel" aria-hidden="true">
<form method="POST" action="/site/delete">
<%== csrf_token_input_html %>
<div class="modal-header">
<button class="close" type="button" data-dismiss="modal" aria-hidden="true">x</button>
<h3 id="deleteSiteLabel">Permanently Delete Site</h3>
</div>
<div class="modal-body">
<strong style="color: red">WARNING: This will permanently delete your web site and Neocities account. There is no undo!</strong>
<p>Delete Site Name: <strong><%= current_site.username %></strong></p>
<p>Confirm your site name by typing it here:</p>
<input class="input-Area" name="username" type="text">
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
<button type="submit" class="btn btn-Action">Permanently Delete Site</button>
</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,8 +1,8 @@
<h2>Change Email</h2>
<form method="POST" action="/change_email">
<form method="POST" action="/settings/change_email">
<%== csrf_token_input_html %>
<p>Current Email: <strong><%= current_site.email %></strong></p>
<p>Current Email: <strong><%= parent_site.email %></strong></p>
<p>New Email:</p>
<input class="input-Area" name="email" type="text">

View file

@ -1,5 +1,5 @@
<h2>Change Password</h2>
<form method="POST" action="/change_password">
<form method="POST" action="/settings/change_password">
<%== csrf_token_input_html %>
<p>Current Password:</p>

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

View file

@ -0,0 +1,32 @@
<h2>Your Sites</h2>
<table class="table">
<% current_site.account_sites_dataset.each do |site| %>
<tr>
<td>
<a href="//<%= site.host %>" target="_blank"><%= site.username %></a>
<% if site.parent? %>
<strong>(parent account)</strong>
<% end %>
</td>
<td>
<a href="/settings/<%= site.username %>">Settings</a>
</td>
</tr>
<% end %>
</table>
<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.account_sites_dataset.count %></strong> new sites remaining.</p>
<form action="/settings/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,4 +0,0 @@
<h2>Custom Domain</h2>
<p>
You can configure a custom domain for your Neocities site! <strong><a href="/custom_domain">Click Here</a></strong> for more information.
</p>

View file

@ -1,7 +1,7 @@
<div class="header-Outro">
<div class="row content single-Col">
<h1>Settings</h1>
<h3 class="subtitle">Manage your account</h3>
<h1>Site Settings for <%= @site.username %></h1>
<h3 class="subtitle"><strong><a href="/settings">Click here</a> to go back to the account menu.</a></strong></h3>
</div>
</div>
@ -9,76 +9,37 @@
<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="#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>
<li><a href="#email" data-toggle="tab">Email</a></li>
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li>
<li><a href="#custom_domain" data-toggle="tab">Custom Domain</a></li>
<li><a href="#username" data-toggle="tab">Username</a></li>
<li><a href="#nsfw" data-toggle="tab">18+</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="plan">
<%== erb :'settings/plan' %>
<div class="tab-pane active" id="profile">
<%== erb :'settings/site/profile' %>
</div>
<div class="tab-pane" id="profile">
<%== erb :'settings/profile' %>
</div>
<div class="tab-pane" id="customdomain">
<%== erb :'settings/custom_domain' %>
</div>
<div class="tab-pane" id="password">
<%== erb :'settings/password' %>
</div>
<div class="tab-pane" id="email">
<%== erb :'settings/email' %>
<div class="tab-pane" id="custom_domain">
<%== erb :'settings/site/custom_domain' %>
</div>
<div class="tab-pane" id="username">
<%== erb :'settings/username' %>
<%== erb :'settings/site/username' %>
</div>
<div class="tab-pane" id="nsfw">
<%== erb :'settings/nsfw' %>
<%== erb :'settings/site/nsfw' %>
</div>
</div>
</div>
@ -116,4 +77,13 @@
<button type="submit" class="btn btn-Action">Permanently Delete Site</button>
</div>
</form>
</div>
</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

@ -0,0 +1,60 @@
<h2>Custom Domain</h2>
<h3 class="subtitle">Add your own domain name to your Neocities site!</h3>
<p>
Adding a custom domain allows you to have a domain name attached to your web site. So if you had a domain like <strong>catsknitting.com</strong>, you could have it point to your Neocities site!
</p>
<p>
You will have to purchase a domain name from a registrar like <a href="https://www.namecheap.com/?aff=53678" target="_blank">Namecheap</a>, and then add an A record to point your domain (catsknitting.com) to the following IP address:
</p>
<p><code>198.27.81.179</code></p>
<p>
If you want to add a <strong>www</strong> subdomain, or use a wildcard that will answer to everything (<strong>*</strong>), you will have to make a CNAME pointing to <strong>catsknitting.com</strong> for <strong>www</strong> and/or <strong>*</strong>.
</p>
<p>
After that, you can add the domain to the box below (just the <strong>catsknitting.com</strong>, don't add any subdomains), and your domain should come online within 5 minutes:
</p>
<form method="POST" action="/settings/<%= @site.username %>/custom_domain">
<%== csrf_token_input_html %>
<input name="domain" type="text" placeholder="catsknitting.com" value="<%= @site.domain %>">
<br>
<input class="btn-Action" type="submit" value="Update Domain">
</form>
<h2>Add SSL Certificate</h2>
<p>
This allows you to add an SSL key and certificate for your domain, enabling encryption for your site (https). It can take up to 5-30 minutes for the changes to go live, so please be patient. All files must be in PEM format. If your certificate is not bundled with the root and intermediate certificates, ask your certificate provider for help on how to do that.
</p>
<% if @site.domain.nil? || @site.domain.empty? %>
<p><strong>Cannot upload SSL certificate until domain is added.</strong></p>
<% else %>
<form method="POST" action="/settings/<%= @site.username %>/ssl" enctype="multipart/form-data">
<%== csrf_token_input_html %>
<p>
<strong>
Status: <%= @site.ssl_installed? ? 'Installed' : 'Inactive' %>
</strong>
</p>
<p>
SSL Key (yourdomain.com.key):
<input name="key" type="file">
</p>
<p>
Bundled Certificates (yourdomain.com-bundle.crt):
<input name="cert" type="file">
</p>
<input class="btn-Action" type="submit" value="Upload SSL Key and Certificate">
</form>
<% end %>

View file

@ -4,14 +4,14 @@
If your site contains objectionable (18+) content, check this box. Your site will not be removed, but it will be listed on a special browse page. We don't have an official policy on what defines 18+ content yet, but basically it's just pornography and lewd/sick/gross images. Thanks for your patience and understanding as we try to find a way to balance out the needs of everyone.
</p>
<form method="POST" action="/change_nsfw">
<form method="POST" action="/settings/<%= @site.username %>/change_nsfw">
<%== csrf_token_input_html %>
<input name="is_nsfw" type="hidden" value="false">
<p>
<strong>
My page has 18+ content:&nbsp;&nbsp;&nbsp;
<input name="is_nsfw" type="checkbox" value="true" style="margin-top: 0px"
<% if current_site.is_nsfw %>checked<% end %>
<% if @site.is_nsfw %>checked<% end %>
>
</strong>
</p>

View file

@ -1,11 +1,11 @@
<h2>Site Profile</h2>
<div>
<form method="POST" action="/settings/profile">
<form method="POST" action="/settings/<%= @site.username %>/profile">
<%== csrf_token_input_html %>
<p>
<input name="site[profile_comments_enabled]" type="hidden" value="true">
<input name="site[profile_comments_enabled]" type="checkbox" value="false"
<% if current_site.profile_comments_enabled == false %>checked<% end %>
<% if @site.profile_comments_enabled == false %>checked<% end %>
> Turn off profile comments
</p>

View file

@ -1,5 +1,5 @@
<h2>Change Site (User) Name</h2>
<form method="POST" action="/change_name">
<form method="POST" action="/settings/<%= @site.username %>/change_name">
<%== csrf_token_input_html %>
<p class="tiny">
It cannot contain spaces, and can only use the following characters: a-z A-Z 0-9 _ -
@ -7,7 +7,7 @@
<p>
Current Name:
<span style="color: green"><strong><%= current_site.username %></strong></span>
<span style="color: green"><strong><%= @site.username %></strong></span>
</p>
<p>

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