Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- app/controllers/admin/settings_controller.rb
- app/models/form/admin_settings.rb

Conflicts caused by upstream refactoring, while we have
flavours and skins, with the flavour_and_skin pseudo-setting.
rebase/4.0.0rc2
Thibaut Girka 2019-03-23 22:10:51 +01:00
commit fcce135d27
15 changed files with 231 additions and 151 deletions

View File

@ -15,7 +15,7 @@ gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.2' gem 'pghero', '~> 2.2'
gem 'dotenv-rails', '~> 2.7' gem 'dotenv-rails', '~> 2.7'
gem 'aws-sdk-s3', '~> 1.33', require: false gem 'aws-sdk-s3', '~> 1.34', require: false
gem 'fog-core', '<= 2.1.0' gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0' gem 'paperclip', '~> 6.0'

View File

@ -76,17 +76,17 @@ GEM
av (0.9.0) av (0.9.0)
cocaine (~> 0.5.3) cocaine (~> 0.5.3)
aws-eventstream (1.0.2) aws-eventstream (1.0.2)
aws-partitions (1.144.0) aws-partitions (1.145.0)
aws-sdk-core (3.48.0) aws-sdk-core (3.48.2)
aws-eventstream (~> 1.0, >= 1.0.2) aws-eventstream (~> 1.0, >= 1.0.2)
aws-partitions (~> 1.0) aws-partitions (~> 1.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-kms (1.15.0) aws-sdk-kms (1.16.0)
aws-sdk-core (~> 3, >= 3.48.0) aws-sdk-core (~> 3, >= 3.48.2)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.33.0) aws-sdk-s3 (1.34.0)
aws-sdk-core (~> 3, >= 3.48.0) aws-sdk-core (~> 3, >= 3.48.2)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.0) aws-sigv4 (~> 1.0)
aws-sigv4 (1.1.0) aws-sigv4 (1.1.0)
@ -336,7 +336,7 @@ GEM
mimemagic (~> 0.3.2) mimemagic (~> 0.3.2)
mario-redis-lock (1.2.1) mario-redis-lock (1.2.1)
redis (>= 3.0.5) redis (>= 3.0.5)
memory_profiler (0.9.12) memory_profiler (0.9.13)
method_source (0.9.2) method_source (0.9.2)
microformats (4.1.0) microformats (4.1.0)
json (~> 2.1) json (~> 2.1)
@ -660,7 +660,7 @@ DEPENDENCIES
active_record_query_trace (~> 1.6) active_record_query_trace (~> 1.6)
addressable (~> 2.6) addressable (~> 2.6)
annotate (~> 2.7) annotate (~> 2.7)
aws-sdk-s3 (~> 1.33) aws-sdk-s3 (~> 1.34)
better_errors (~> 2.5) better_errors (~> 2.5)
binding_of_caller (~> 0.7) binding_of_caller (~> 0.7)
bootsnap (~> 1.4) bootsnap (~> 1.4)

View File

@ -2,94 +2,29 @@
module Admin module Admin
class SettingsController < BaseController class SettingsController < BaseController
ADMIN_SETTINGS = %w(
site_contact_username
site_contact_email
site_title
site_short_description
site_description
site_extended_description
site_terms
registrations_mode
closed_registrations_message
open_deletion
timeline_preview
show_staff_badge
bootstrap_timeline_accounts
flavour
skin
flavour_and_skin
thumbnail
hero
mascot
min_invite_role
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
preview_sensitive_media
custom_css
profile_directory
hide_followers_count
).freeze
BOOLEAN_SETTINGS = %w(
open_deletion
timeline_preview
show_staff_badge
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
preview_sensitive_media
profile_directory
hide_followers_count
).freeze
UPLOAD_SETTINGS = %w(
thumbnail
hero
mascot
).freeze
def edit def edit
authorize :settings, :show? authorize :settings, :show?
@admin_settings = Form::AdminSettings.new @admin_settings = Form::AdminSettings.new
end end
def update def update
authorize :settings, :update? authorize :settings, :update?
settings = settings_params @admin_settings = Form::AdminSettings.new(settings_params)
flavours_and_skin = settings.delete('flavour_and_skin')
if flavours_and_skin
settings['flavour'], settings['skin'] = flavours_and_skin.split('/', 2)
end
settings.each do |key, value| if @admin_settings.save
if UPLOAD_SETTINGS.include?(key) flash[:notice] = I18n.t('generic.changes_saved_msg')
upload = SiteUpload.where(var: key).first_or_initialize(var: key) redirect_to edit_admin_settings_path
upload.update(file: value) else
else render :edit
setting = Setting.where(var: key).first_or_initialize(var: key)
setting.update(value: value_for_update(key, value))
end
end end
flash[:notice] = I18n.t('generic.changes_saved_msg')
redirect_to edit_admin_settings_path
end end
private private
def settings_params def settings_params
params.require(:form_admin_settings).permit(ADMIN_SETTINGS) params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
end
def value_for_update(key, value)
if BOOLEAN_SETTINGS.include?(key)
value == '1'
else
value
end
end end
end end
end end

View File

@ -475,6 +475,42 @@ code {
} }
} }
} }
&__overlay-area {
position: relative;
&__overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: rgba($ui-base-color, 0.65);
backdrop-filter: blur(2px);
border-radius: 4px;
&__content {
text-align: center;
&.rich-formatting {
&,
p {
color: $primary-text-color;
}
}
}
}
}
}
.block-icon {
display: block;
margin: 0 auto;
margin-bottom: 10px;
font-size: 24px;
} }
.flash-message { .flash-message {

View File

@ -3,57 +3,108 @@
class Form::AdminSettings class Form::AdminSettings
include ActiveModel::Model include ActiveModel::Model
delegate( KEYS = %i(
:site_contact_username, site_contact_username
:site_contact_username=, site_contact_email
:site_contact_email, site_title
:site_contact_email=, site_short_description
:site_title, site_description
:site_title=, site_extended_description
:site_short_description, site_terms
:site_short_description=, registrations_mode
:site_description, closed_registrations_message
:site_description=, open_deletion
:site_extended_description, timeline_preview
:site_extended_description=, show_staff_badge
:site_terms, bootstrap_timeline_accounts
:site_terms=, flavour
:registrations_mode, skin
:registrations_mode=, min_invite_role
:closed_registrations_message, activity_api_enabled
:closed_registrations_message=, peers_api_enabled
:open_deletion, show_known_fediverse_at_about_page
:open_deletion=, preview_sensitive_media
:timeline_preview, custom_css
:timeline_preview=, profile_directory
:show_staff_badge, hide_followers_count
:show_staff_badge=, flavour_and_skin
:bootstrap_timeline_accounts, ).freeze
:bootstrap_timeline_accounts=,
:hide_followers_count, BOOLEAN_KEYS = %i(
:hide_followers_count=, open_deletion
:flavour, timeline_preview
:flavour=, show_staff_badge
:skin, activity_api_enabled
:skin=, peers_api_enabled
:min_invite_role, show_known_fediverse_at_about_page
:min_invite_role=, preview_sensitive_media
:activity_api_enabled, profile_directory
:activity_api_enabled=, hide_followers_count
:peers_api_enabled, ).freeze
:peers_api_enabled=,
:show_known_fediverse_at_about_page, UPLOAD_KEYS = %i(
:show_known_fediverse_at_about_page=, thumbnail
:preview_sensitive_media, hero
:preview_sensitive_media=, mascot
:custom_css, ).freeze
:custom_css=,
:profile_directory, PSEUDO_KEYS = %i(
:profile_directory=, flavour_and_skin
to: Setting ).freeze
)
attr_accessor(*KEYS)
validates :site_short_description, :site_description, :site_extended_description, :site_terms, :closed_registrations_message, html: true
validates :registrations_mode, inclusion: { in: %w(open approved none) }
validates :min_invite_role, inclusion: { in: %w(disabled user moderator admin) }
validates :site_contact_email, :site_contact_username, presence: true
validates :site_contact_username, existing_username: true
validates :bootstrap_timeline_accounts, existing_username: { multiple: true }
def initialize(_attributes = {})
super
initialize_attributes
end
def save
return false unless valid?
KEYS.each do |key|
next if PSEUDO_KEYS.include?(key)
value = instance_variable_get("@#{key}")
if UPLOAD_KEYS.include?(key)
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
upload.update(file: value)
else
setting = Setting.where(var: key).first_or_initialize(var: key)
setting.update(value: typecast_value(key, value))
end
end
end
def flavour_and_skin def flavour_and_skin
"#{Setting.flavour}/#{Setting.skin}" "#{Setting.flavour}/#{Setting.skin}"
end end
def flavour_and_skin=(value)
@flavour, @skin = value.split('/', 2)
end
private
def initialize_attributes
KEYS.each do |key|
next if PSEUDO_KEYS.include?(key)
instance_variable_set("@#{key}", Setting.public_send(key)) if instance_variable_get("@#{key}").nil?
end
end
def typecast_value(key, value)
if BOOLEAN_KEYS.include?(key)
value == '1'
else
value
end
end
end end

View File

@ -124,7 +124,8 @@ class User < ApplicationRecord
end end
def confirm def confirm
new_user = !confirmed? new_user = !confirmed?
self.approved = true if open_registrations?
super super
@ -136,7 +137,8 @@ class User < ApplicationRecord
end end
def confirm! def confirm!
new_user = !confirmed? new_user = !confirmed?
self.approved = true if open_registrations?
skip_confirmation! skip_confirmation!
save! save!
@ -264,7 +266,11 @@ class User < ApplicationRecord
private private
def set_approved def set_approved
self.approved = Setting.registrations_mode == 'open' || invited? self.approved = open_registrations? || invited?
end
def open_registrations?
Setting.registrations_mode == 'open'
end end
def sanitize_languages def sanitize_languages

View File

@ -8,6 +8,7 @@ class InstancePresenter
:site_description, :site_description,
:site_extended_description, :site_extended_description,
:site_terms, :site_terms,
:closed_registrations_message,
to: Setting to: Setting
) )

View File

@ -11,14 +11,14 @@ class VoteService < BaseService
@choices = choices @choices = choices
@votes = [] @votes = []
return if @poll.expired?
ApplicationRecord.transaction do ApplicationRecord.transaction do
@choices.each do |choice| @choices.each do |choice|
@votes << @poll.votes.create!(account: @account, choice: choice) @votes << @poll.votes.create!(account: @account, choice: choice)
end end
end end
ActivityTracker.increment('activity:interactions')
if @poll.account.local? if @poll.account.local?
distribute_poll! distribute_poll!
else else

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class ExistingUsernameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
if options[:multiple]
missing_usernames = value.split(',').map { |username| username unless Account.find_local(username) }.compact
record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: missing_usernames.join(', '))) if missing_usernames.any?
else
record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) unless Account.find_local(value)
end
end
private
def valid_html?(str)
Nokogiri::HTML.fragment(str).to_s == str
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class HtmlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
record.errors.add(attribute, I18n.t('html_validator.invalid_markup')) unless valid_html?(value)
end
private
def valid_html?(str)
Nokogiri::HTML.fragment(str).to_s == str
end
end

View File

@ -1,16 +1,23 @@
= simple_form_for(new_user, url: user_registration_path) do |f| = simple_form_for(new_user, url: user_registration_path) do |f|
%p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_hostname)) .simple_form__overlay-area
%p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_hostname))
.fields-group .fields-group
= f.simple_fields_for :account do |account_fields| = f.simple_fields_for :account do |account_fields|
= account_fields.input :username, wrapper: :with_label, autofocus: true, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username') }, append: "@#{site_hostname}", hint: false, disabled: closed_registrations? = account_fields.input :username, wrapper: :with_label, autofocus: true, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username') }, append: "@#{site_hostname}", hint: false, disabled: closed_registrations?
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations? = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations? = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations? = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: closed_registrations?
.fields-group .fields-group
= f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), disabled: closed_registrations? = f.input :agreement, as: :boolean, wrapper: :with_label, label: t('auth.checkbox_agreement_html', rules_path: about_more_path, terms_path: terms_path), disabled: closed_registrations?
.actions .actions
= f.button :button, sign_up_message, type: :submit, class: 'button button-primary', disabled: closed_registrations? = f.button :button, sign_up_message, type: :submit, class: 'button button-primary', disabled: closed_registrations?
- if closed_registrations? && @instance_presenter.closed_registrations_message.present?
.simple_form__overlay-area__overlay
.simple_form__overlay-area__overlay__content.rich-formatting
.block-icon= fa_icon 'warning'
= @instance_presenter.closed_registrations_message.html_safe

View File

@ -2,6 +2,7 @@
= t('admin.settings.title') = t('admin.settings.title')
= simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch } do |f| = simple_form_for @admin_settings, url: admin_settings_path, html: { method: :patch } do |f|
= render 'shared/error_messages', object: @admin_settings
.fields-group .fields-group
= f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title') = f.input :site_title, wrapper: :with_label, label: t('admin.settings.site_title')

View File

@ -431,13 +431,13 @@ en:
desc_html: Show a staff badge on a user page desc_html: Show a staff badge on a user page
title: Show staff badge title: Show staff badge
site_description: site_description:
desc_html: Introductory paragraph on the frontpage. Describe what makes this Mastodon server special and anything else important. You can use HTML tags, in particular <code>&lt;a&gt;</code> and <code>&lt;em&gt;</code>. desc_html: Introductory paragraph on the API. Describe what makes this Mastodon server special and anything else important. You can use HTML tags, in particular <code>&lt;a&gt;</code> and <code>&lt;em&gt;</code>.
title: Server description title: Server description
site_description_extended: site_description_extended:
desc_html: A good place for your code of conduct, rules, guidelines and other things that set your server apart. You can use HTML tags desc_html: A good place for your code of conduct, rules, guidelines and other things that set your server apart. You can use HTML tags
title: Custom extended information title: Custom extended information
site_short_description: site_short_description:
desc_html: Displayed in sidebar and meta tags. Describe what Mastodon is and what makes this server special in a single paragraph. If empty, defaults to server description. desc_html: Displayed in sidebar and meta tags. Describe what Mastodon is and what makes this server special in a single paragraph.
title: Short server description title: Short server description
site_terms: site_terms:
desc_html: You can write your own privacy policy, terms of service or other legalese. You can use HTML tags desc_html: You can write your own privacy policy, terms of service or other legalese. You can use HTML tags
@ -589,6 +589,9 @@ en:
content: We're sorry, but something went wrong on our end. content: We're sorry, but something went wrong on our end.
title: This page is not correct title: This page is not correct
noscript_html: To use the Mastodon web application, please enable JavaScript. Alternatively, try one of the <a href="%{apps_path}">native apps</a> for Mastodon for your platform. noscript_html: To use the Mastodon web application, please enable JavaScript. Alternatively, try one of the <a href="%{apps_path}">native apps</a> for Mastodon for your platform.
existing_username_validator:
not_found: could not find a local user with that username
not_found_multiple: could not find %{usernames}
exports: exports:
archive_takeout: archive_takeout:
date: Date date: Date
@ -637,6 +640,8 @@ en:
validation_errors: validation_errors:
one: Something isn't quite right yet! Please review the error below one: Something isn't quite right yet! Please review the error below
other: Something isn't quite right yet! Please review %{count} errors below other: Something isn't quite right yet! Please review %{count} errors below
html_validator:
invalid_markup: contains invalid HTML markup
identity_proofs: identity_proofs:
active: Active active: Active
authorize: Yes, authorize authorize: Yes, authorize

View File

@ -14,7 +14,7 @@ SimpleNavigation::Configuration.run do |navigation|
settings.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url settings.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url
settings.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url settings.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url
settings.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url settings.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url
settings.item :identity_proofs, safe_join([fa_icon('key fw'), t('settings.identity_proofs')]), settings_identity_proofs_path, highlights_on: %r{/settings/identity_proofs*} settings.item :identity_proofs, safe_join([fa_icon('key fw'), t('settings.identity_proofs')]), settings_identity_proofs_path, highlights_on: %r{/settings/identity_proofs*}, if: proc { current_account.identity_proofs.exists? }
end end
primary.item :flavours, safe_join([fa_icon('paint-brush fw'), t('settings.flavours')]), settings_flavours_url do |flavours| primary.item :flavours, safe_join([fa_icon('paint-brush fw'), t('settings.flavours')]), settings_flavours_url do |flavours|
@ -43,7 +43,7 @@ SimpleNavigation::Configuration.run do |navigation|
primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |admin| primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |admin|
admin.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url admin.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url
admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? } admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings}
admin.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis} admin.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis}
admin.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/relays} admin.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/relays}
admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? } admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? }

View File

@ -19,6 +19,10 @@ RSpec.describe Admin::SettingsController, type: :controller do
end end
describe 'PUT #update' do describe 'PUT #update' do
before do
allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true)
end
describe 'for a record that doesnt exist' do describe 'for a record that doesnt exist' do
around do |example| around do |example|
before = Setting.site_extended_description before = Setting.site_extended_description