Split Settings::Community from SiteConfig (#13403)

* Add settings_community_contents model

* Add settings_communities model

* Update usage

* Add controller and update code

* Add e2e test

* Add data update script

* Update schema.rb

* Fix specs

* PR feedback

* Remove experience_* from Settings::Community

* Update spec

* Fix spec
This commit is contained in:
Michael Kohl 2021-04-26 15:46:35 +07:00 committed by GitHub
parent 6c7df12198
commit 610f6151e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 411 additions and 216 deletions

View file

@ -0,0 +1,36 @@
module Admin
module Settings
class CommunitiesController < Admin::ApplicationController
def create
errors = upsert_config(settings_params)
if errors.none?
Audit::Logger.log(:internal, current_user, params.dup)
redirect_to admin_config_path, notice: "Site configuration was successfully updated."
else
redirect_to admin_config_path, alert: "😭 #{errors.to_sentence}"
end
end
private
def settings_params
params
.require(:settings_community)
.permit(*::Settings::Community.keys)
end
def upsert_config(configs)
errors = []
configs.each do |key, value|
::Settings::Community.public_send("#{key}=", value) if value.present?
rescue ActiveRecord::RecordInvalid => e
errors << e.message
next
end
errors
end
end
end
end

View file

@ -4,13 +4,13 @@ module VerifySetupCompleted
module_function
MANDATORY_CONFIGS = %i[
community_name
community_description
MANDATORY_CONFIGS = {
community_name: Settings::Community,
community_description: Settings::Community,
suggested_tags
suggested_users
].freeze
suggested_tags: SiteConfig,
suggested_users: SiteConfig
}.freeze
included do
# rubocop:disable Rails/LexicallyScopedActionFilter
@ -23,7 +23,9 @@ module VerifySetupCompleted
end
def missing_configs
@missing_configs ||= MANDATORY_CONFIGS.reject { |config| SiteConfig.public_send(config).present? }
@missing_configs ||= MANDATORY_CONFIGS.reject do |config, settings_model|
settings_model.public_send(config).present?
end.keys
end
private

View file

@ -15,11 +15,11 @@ class EmailSubscriptionsController < ApplicationController
def preferred_email_name
{
email_digest_periodic: "#{SiteConfig.community_name} digest emails",
email_digest_periodic: "#{Settings::Community.community_name} digest emails",
email_comment_notifications: "comment notifications",
email_follower_notifications: "follower notifications",
email_mention_notifications: "mention notifications",
email_connect_messages: "#{SiteConfig.community_name} connect messages",
email_connect_messages: "#{Settings::Community.community_name} connect messages",
email_unread_notifications: "unread notifications",
email_badge_notifications: "badge notifications"
}.freeze

View file

@ -122,6 +122,7 @@ class SearchController < ApplicationController
# TODO: [@rhymes] the homepage feed uses `feed_content_search` as an index,
# we should eventually move it to a JSON result
# in ArticlesController#Homepage or HomepageController#show
# rubocop:disable Metric/PerceivedComplexity
def feed_content
class_name = feed_params[:class_name].to_s.inquiry
@ -176,6 +177,7 @@ class SearchController < ApplicationController
render json: { result: result }
end
# rubocop:enable Metric/PerceivedComplexity
def reactions
if FeatureFlag.enabled?(:search_2_reading_list)

View file

@ -168,11 +168,11 @@ module ApplicationHelper
end
def community_name
@community_name ||= SiteConfig.community_name
@community_name ||= Settings::Community.community_name
end
def community_emoji
@community_emoji ||= SiteConfig.community_emoji
@community_emoji ||= Settings::Community.community_emoji
end
def release_adjusted_cache_key(path)
@ -183,7 +183,7 @@ module ApplicationHelper
end
def copyright_notice
start_year = SiteConfig.community_copyright_start_year.to_s
start_year = Settings::Community.copyright_start_year.to_s
current_year = Time.current.year.to_s
return start_year if current_year == start_year
return current_year if start_year.strip.length < 4 # 978 is not a valid year!
@ -206,7 +206,7 @@ module ApplicationHelper
end
def community_members_label
SiteConfig.community_member_label.pluralize
Settings::Community.member_label.pluralize
end
def meta_keywords_default

View file

@ -2,44 +2,44 @@ module FeedbackMessagesHelper
OFFENDER_EMAIL_BODY = <<~HEREDOC.freeze
Hello,
It has been brought to our attention that you have violated the #{SiteConfig.community_name} Code of Conduct and/or Terms of Use.
It has been brought to our attention that you have violated the #{Settings::Community.community_name} Code of Conduct and/or Terms of Use.
If this behavior continues, we may need to suspend your #{SiteConfig.community_name} account.
If this behavior continues, we may need to suspend your #{Settings::Community.community_name} account.
If you think that there's been a mistake, please reply to this email and we will take another look.
#{SiteConfig.community_name} Team
#{Settings::Community.community_name} Team
HEREDOC
REPORTER_EMAIL_BODY = <<~HEREDOC.freeze
Hi there,
Thank you for flagging content that may be in violation of the #{SiteConfig.community_name} Code of Conduct and/or our Terms of Use. We are looking into your report and will take appropriate action.
Thank you for flagging content that may be in violation of the #{Settings::Community.community_name} Code of Conduct and/or our Terms of Use. We are looking into your report and will take appropriate action.
We appreciate your help as we work to foster a positive and inclusive environment for all!
#{SiteConfig.community_name} Team
#{Settings::Community.community_name} Team
HEREDOC
AFFECTED_EMAIL_BODY = <<~HEREDOC.freeze
Hi there,
We noticed some comments (made by others) on your post that violate the #{SiteConfig.community_name} Code of Conduct and/or our Terms of Use. We have zero tolerance for such behavior and are taking appropriate action.
We noticed some comments (made by others) on your post that violate the #{Settings::Community.community_name} Code of Conduct and/or our Terms of Use. We have zero tolerance for such behavior and are taking appropriate action.
Thanks for being awesome, and please don't hesitate to email us with any questions! We welcome all feedback and ideas as we continue working to foster an open and inclusive community.
#{SiteConfig.community_name} Team
#{Settings::Community.community_name} Team
HEREDOC
def offender_email_details
body = format(OFFENDER_EMAIL_BODY)
{ subject: "#{SiteConfig.community_name} Code of Conduct Violation", body: body }.freeze
{ subject: "#{Settings::Community.community_name} Code of Conduct Violation", body: body }.freeze
end
def reporter_email_details
body = format(REPORTER_EMAIL_BODY)
{ subject: "#{SiteConfig.community_name} Report", body: body }.freeze
{ subject: "#{Settings::Community.community_name} Report", body: body }.freeze
end
def affected_email_details
body = format(AFFECTED_EMAIL_BODY)
{ subject: "Courtesy Notice from #{SiteConfig.community_name}", body: body }.freeze
{ subject: "Courtesy Notice from #{Settings::Community.community_name}", body: body }.freeze
end
end

View file

@ -0,0 +1,36 @@
module Constants
module Settings
module Community
DETAILS = {
community_description: {
description: "Used in meta description tags etc.",
placeholder: "A fabulous community of kind and welcoming people."
},
community_emoji: {
description: "Used in the title tags across the site alongside the community name",
placeholder: ""
},
community_name: {
description: "Used as the primary name for your Forem, e.g. DEV, DEV Community, The DEV Community, etc.",
placeholder: "New Forem"
},
copyright_start_year: {
description: "Used to mark the year this forem was started.",
placeholder: Time.zone.today.year.to_s
},
member_label: {
description: "Used to determine what a member will be called e.g developer, hobbyist etc.",
placeholder: "user"
},
staff_user_id: {
description: "Account ID which acts as automated 'staff'— used principally for welcome thread.",
placeholder: ""
},
tagline: {
description: "Used in signup modal.",
placeholder: "We're a place where coders share, stay up-to-date and grow their careers."
}
}.freeze
end
end
end

View file

@ -4,26 +4,6 @@ module Constants
SVG_PLACEHOLDER = "<svg ...></svg>".freeze
DETAILS = {
community_copyright_start_year: {
description: "Used to mark the year this forem was started.",
placeholder: Time.zone.today.year.to_s
},
community_description: {
description: "Used in meta description tags etc.",
placeholder: "A fabulous community of kind and welcoming people."
},
community_emoji: {
description: "Used in the title tags across the site alongside the community name",
placeholder: ""
},
community_member_label: {
description: "Used to determine what a member will be called e.g developer, hobbyist etc.",
placeholder: "user"
},
community_name: {
description: "Used as the primary name for your Forem, e.g. DEV, DEV Community, The DEV Community, etc.",
placeholder: "New Forem"
},
credit_prices_in_cents: {
small: {
description: "Price for small credit purchase (<10 credits).",
@ -49,14 +29,6 @@ module Constants
description: "Email address",
placeholder: ""
},
experience_low: {
description: "The label for the bottom of the experience level range of a post",
placeholder: "Total Newbies"
},
experience_high: {
description: "The label for the top of the experience level range of a post",
placeholder: "Senior Devs"
},
favicon_url: {
description: "Used as the site favicon",
placeholder: IMAGE_PLACEHOLDER
@ -151,10 +123,6 @@ module Constants
description: "Determines the heading text of the main sponsors sidebar above the list of sponsors.",
placeholder: "Community Sponsors"
},
staff_user_id: {
description: "Account ID which acts as automated 'staff'— used principally for welcome thread.",
placeholder: ""
},
stripe_api_key: {
description: "Secret Stripe key for receiving payments. " \
"See: https://stripe.com/docs/keys",
@ -184,10 +152,6 @@ module Constants
description: "Minimum score needed for a post to show up on default tag page.",
placeholder: "0"
},
tagline: {
description: "Used in signup modal.",
placeholder: "We're a place where coders share, stay up-to-date and grow their careers."
},
twitter_hashtag: {
description: "Used as the twitter hashtag of the community",
placeholder: "#DEVCommunity"

View file

@ -34,7 +34,7 @@ class RedditTag < LiquidTagBase
# Requests to Reddit require a custom `User-Agent` header to prevent 429 errors
json = HTTParty.get("#{@url}.json",
headers: { "User-Agent" => "#{SiteConfig.community_name} (#{URL.url})" })
headers: { "User-Agent" => "#{Settings::Community.community_name} (#{URL.url})" })
# The JSON response is an array with two items.
# The first one is the post itself, the second one are the comments

View file

@ -26,7 +26,7 @@ class DigestMailer < ApplicationMailer
end
def email_end_phrase
community_name = SiteConfig.community_name
community_name = Settings::Community.community_name
# "more trending posts" won the previous split test
# Included more often as per explore-exploit algorithm
[

View file

@ -46,7 +46,7 @@ class NotifyMailer < ApplicationMailer
@unread_notifications_count = @user.notifications.unread.count
@unsubscribe = generate_unsubscribe_token(@user.id, :email_unread_notifications)
subject = "🔥 You have #{@unread_notifications_count} unread notifications on #{SiteConfig.community_name}"
subject = "🔥 You have #{@unread_notifications_count} unread notifications on #{Settings::Community.community_name}"
mail(to: @user.email, subject: subject)
end
@ -66,7 +66,7 @@ class NotifyMailer < ApplicationMailer
end
def feedback_response_email
mail(to: params[:email_to], subject: "Thanks for your report on #{SiteConfig.community_name}")
mail(to: params[:email_to], subject: "Thanks for your report on #{Settings::Community.community_name}")
end
def feedback_message_resolution_email
@ -108,7 +108,7 @@ class NotifyMailer < ApplicationMailer
def account_deleted_email
@name = params[:name]
subject = "#{SiteConfig.community_name} - Account Deletion Confirmation"
subject = "#{Settings::Community.community_name} - Account Deletion Confirmation"
mail(to: params[:email], subject: subject)
end
@ -125,7 +125,7 @@ class NotifyMailer < ApplicationMailer
@name = user.name
@token = params[:token]
subject = "#{SiteConfig.community_name} - Account Deletion Requested"
subject = "#{Settings::Community.community_name} - Account Deletion Requested"
mail(to: user.email, subject: subject)
end
@ -149,13 +149,13 @@ class NotifyMailer < ApplicationMailer
def trusted_role_email
@user = params[:user]
subject = "Congrats! You're now a \"trusted\" user on #{SiteConfig.community_name}!"
subject = "Congrats! You're now a \"trusted\" user on #{Settings::Community.community_name}!"
mail(to: @user.email, subject: subject)
end
def subjects
{
new_follower_email: "just followed you on #{SiteConfig.community_name}".freeze
new_follower_email: "just followed you on #{Settings::Community.community_name}".freeze
}.freeze
end
end

View file

@ -1,6 +1,6 @@
class VerificationMailer < ApplicationMailer
default from: lambda {
"#{SiteConfig.community_name} Email Verification <#{SiteConfig.email_addresses[:default]}>"
"#{Settings::Community.community_name} Email Verification <#{SiteConfig.email_addresses[:default]}>"
}
def account_ownership_verification_email
@ -8,6 +8,6 @@ class VerificationMailer < ApplicationMailer
email_authorization = EmailAuthorization.create!(user: @user, type_of: "account_ownership")
@confirmation_token = email_authorization.confirmation_token
mail(to: @user.email, subject: "Verify Your #{SiteConfig.community_name} Account Ownership")
mail(to: @user.email, subject: "Verify Your #{Settings::Community.community_name} Account Ownership")
end
end

View file

@ -181,7 +181,8 @@ class Article < ApplicationRecord
published
.where(user_id: User.with_role(:super_admin)
.union(User.with_role(:admin))
.union(id: [SiteConfig.staff_user_id, Settings::Mascot.mascot_user_id].compact)
.union(id: [Settings::Community.staff_user_id,
Settings::Mascot.mascot_user_id].compact)
.select(:id)).order(published_at: :desc).tagged_with(tag_name)
}

View file

@ -0,0 +1,19 @@
module Settings
class Community < RailsSettings::Base
self.table_name = :settings_communities
# The configuration is cached, change this if you want to force update
# the cache, or call Settings::Community.clear_cache
cache_prefix { "v1" }
field :copyright_start_year,
type: :integer,
default: ApplicationConfig["COMMUNITY_COPYRIGHT_START_YEAR"] || Time.zone.today.year
field :community_description, type: :string
field :community_emoji, type: :string, default: "🌱", validates: { emoji_only: true }
field :community_name, type: :string, default: ApplicationConfig["COMMUNITY_NAME"] || "New Forem"
field :member_label, type: :string, default: "user"
field :staff_user_id, type: :integer, default: 1
field :tagline, type: :string
end
end

View file

@ -48,6 +48,8 @@ class SiteConfig < RailsSettings::Base
field :campaign_articles_require_approval, type: :boolean, default: 0
field :campaign_articles_expiry_time, type: :integer, default: 4
# Community Content
# NOTE: @citizen428 All these settings will be removed once we full migrated
# to Settings::Community across the fleet.
field :community_name, type: :string, default: ApplicationConfig["COMMUNITY_NAME"] || "New Forem"
field :community_emoji, type: :string, default: "🌱", validates: { emoji_only: true }
# collective_noun and collective_noun_disabled have been added back temporarily for

View file

@ -308,7 +308,7 @@ class User < ApplicationRecord
after_commit :remove_from_elasticsearch, on: [:destroy]
def self.dev_account
find_by(id: SiteConfig.staff_user_id)
find_by(id: Settings::Community.staff_user_id)
end
def self.mascot_account

View file

@ -11,7 +11,7 @@ module Badges
}.freeze
MESSAGE_TEMPLATE =
"Happy #{SiteConfig.community_name} birthday! " \
"Happy #{Settings::Community.community_name} birthday! " \
"Can you believe it's been %<years>d %<noun>s already?!".freeze
def self.call
@ -19,7 +19,7 @@ module Badges
end
def call
total_years = Time.current.year - SiteConfig.community_copyright_start_year.to_i
total_years = Time.current.year - Settings::Community.copyright_start_year.to_i
(1..total_years).each do |i|
::Badges::Award.call(
User.registered.where(created_at: i.year.ago - 2.days...i.year.ago),

View file

@ -22,7 +22,7 @@ module EdgeCache
bust_tag_pages(cache_bust, article)
end
def self.paths_for(article)
def self.paths_for(article, &block)
paths = [
article.path,
"/#{article.user.username}",
@ -35,9 +35,7 @@ module EdgeCache
"/api/articles/#{article.id}",
]
paths.each do |path|
yield path
end
paths.each(&block)
yield "/#{article.organization.slug}" if article.organization.present?

View file

@ -1,6 +1,5 @@
module Settings
class Upsert
EMOJI_ONLY_FIELDS = %w[community_emoji].freeze
VALID_DOMAIN = /^[a-zA-Z0-9]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/.freeze
PARAMS_TO_BE_CLEANED = %i[sidebar_tags suggested_tags suggested_users].freeze

View file

@ -36,41 +36,42 @@
<%= form_for(SiteConfig.new, url: admin_config_path) do |f| %>
<fieldset class="grid gap-4">
<% VerifySetupCompleted::MANDATORY_CONFIGS.each do |config_key| %>
<% VerifySetupCompleted::MANDATORY_CONFIGS.each do |config_key, settings_model| %>
<%# we need to list the config as separate fields if the data structure is a Hash %>
<% if SiteConfig.public_send(config_key).is_a?(Hash) %>
<% placeholder_module = Constants.const_get(settings_model.name) %>
<% if settings_model.public_send(config_key).is_a?(Hash) %>
<%= admin_config_label config_key.to_s %>
<div class="form-group">
<%= f.fields_for config_key do |a_field| %>
<% SiteConfig.public_send(config_key).each do |key, value| %>
<% settings_model.public_send(config_key).each do |key, value| %>
<div class="crayons-field">
<%= admin_config_label key.to_s %>
<%= admin_config_description "#{key} #{config_key.to_s.tr! '_', ' '}" %>
<%= a_field.text_field key,
class: "crayons-textfield",
value: SiteConfig.public_send(config_key)[key],
placeholder: Constants::SiteConfig::DETAILS[config_key][:placeholder] %>
value: settings_model.public_send(config_key)[key],
placeholder: placeholer_module::DETAILS[config_key][:placeholder] %>
</div>
<% end %>
<% end %>
</div>
<% elsif SiteConfig.public_send(config_key).is_a?(Array) %>
<% elsif settings_model.public_send(config_key).is_a?(Array) %>
<div class="crayons-field">
<%= admin_config_label config_key %>
<%= admin_config_description Constants::SiteConfig::DETAILS[config_key][:description] %>
<%= admin_config_description placeholder_module::DETAILS[config_key][:description] %>
<%= f.text_field config_key,
class: "crayons-textfield",
value: SiteConfig.public_send(config_key).join(","),
placeholder: Constants::SiteConfig::DETAILS[config_key][:placeholder] %>
value: settings_model.public_send(config_key).join(","),
placeholder: placeholder_module::DETAILS[config_key][:placeholder] %>
</div>
<% else %>
<div class="crayons-field">
<%= admin_config_label config_key %>
<%= admin_config_description Constants::SiteConfig::DETAILS[config_key][:description] %>
<%= admin_config_description placeholder_module::DETAILS[config_key][:description] %>
<%= f.text_field config_key,
class: "form-control",
value: SiteConfig.public_send(config_key),
placeholder: Constants::SiteConfig::DETAILS[config_key][:placeholder] %>
value: settings_model.public_send(config_key),
placeholder: placeholder_module::DETAILS[config_key][:placeholder] %>
</div>
<% end %>
<% end %>
@ -453,7 +454,7 @@
</div>
<% end %>
<%= form_for(SiteConfig.new, url: admin_config_path) do |f| %>
<%= form_for(Settings::Community.new, url: admin_settings_communities_path) do |f| %>
<div class="card mt-3">
<%= render partial: "admin/shared/card_header",
locals: {
@ -465,84 +466,66 @@
<div id="contentBodyContainer" class="card-body collapse hide" aria-labelledby="contentBodyContainer">
<fieldset class="grid gap-4">
<div class="crayons-field">
<%= admin_config_label :community_name %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:community_name][:description] %>
<%= admin_config_label :community_name, model: Settings::Community %>
<%= admin_config_description Constants::Settings::Community::DETAILS[:community_name][:description] %>
<%= f.text_field :community_name,
class: "crayons-textfield",
value: SiteConfig.community_name,
placeholder: Constants::SiteConfig::DETAILS[:community_name][:placeholder] %>
value: Settings::Community.community_name,
placeholder: Constants::Settings::Community::DETAILS[:community_name][:placeholder] %>
</div>
<div class="crayons-field">
<%= admin_config_label :community_emoji %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:community_emoji][:description] %>
<%= admin_config_label :community_emoji, model: Settings::Community %>
<%= admin_config_description Constants::Settings::Community::DETAILS[:community_emoji][:description] %>
<%= f.text_field :community_emoji,
class: "crayons-textfield",
value: SiteConfig.community_emoji,
placeholder: Constants::SiteConfig::DETAILS[:community_emoji][:placeholder] %>
value: Settings::Community.community_emoji,
placeholder: Constants::Settings::Community::DETAILS[:community_emoji][:placeholder] %>
</div>
<div class="crayons-field">
<%= admin_config_label :tagline %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:tagline][:description] %>
<%= admin_config_label :tagline, model: Settings::Community %>
<%= admin_config_description Constants::Settings::Community::DETAILS[:tagline][:description] %>
<%= f.text_field :tagline,
class: "crayons-textfield",
value: SiteConfig.tagline,
placeholder: Constants::SiteConfig::DETAILS[:tagline][:placeholder] %>
value: Settings::Community.tagline,
placeholder: Constants::Settings::Community::DETAILS[:tagline][:placeholder] %>
</div>
<div class="crayons-field">
<%= admin_config_label :community_description %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:community_description][:description] %>
<%= admin_config_label :community_description, model: Settings::Community %>
<%= admin_config_description Constants::Settings::Community::DETAILS[:community_description][:description] %>
<%= f.text_field :community_description,
class: "crayons-textfield",
value: SiteConfig.community_description,
placeholder: Constants::SiteConfig::DETAILS[:community_description][:placeholder] %>
value: Settings::Community.community_description,
placeholder: Constants::Settings::Community::DETAILS[:community_description][:placeholder] %>
</div>
<div class="crayons-field">
<%= admin_config_label :community_member_label %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:community_member_label][:description] %>
<%= f.text_field :community_member_label,
<%= admin_config_label :member_label, model: Settings::Community %>
<%= admin_config_description Constants::Settings::Community::DETAILS[:member_label][:description] %>
<%= f.text_field :member_label,
class: "crayons-textfield",
value: SiteConfig.community_member_label,
placeholder: Constants::SiteConfig::DETAILS[:community_member_label][:placeholder] %>
value: Settings::Community.member_label,
placeholder: Constants::Settings::Community::DETAILS[:member_label][:placeholder] %>
</div>
<div class="crayons-field">
<%= admin_config_label :community_copyright_start_year %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:community_copyright_start_year][:description] %>
<%= f.text_field :community_copyright_start_year,
<%= admin_config_label :copyright_start_year, model: Settings::Community %>
<%= admin_config_description Constants::Settings::Community::DETAILS[:copyright_start_year][:description] %>
<%= f.text_field :copyright_start_year,
class: "crayons-textfield",
value: SiteConfig.community_copyright_start_year,
placeholder: Constants::SiteConfig::DETAILS[:community_copyright_start_year][:placeholder] %>
value: Settings::Community.copyright_start_year,
placeholder: Constants::Settings::Community::DETAILS[:copyright_start_year][:placeholder] %>
</div>
<div class="crayons-field">
<%= admin_config_label :staff_user_id %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:staff_user_id][:description] %>
<%= admin_config_label :staff_user_id, model: Settings::Community %>
<%= admin_config_description Constants::Settings::Community::DETAILS[:staff_user_id][:description] %>
<%= f.text_field :staff_user_id,
class: "crayons-textfield",
value: SiteConfig.staff_user_id,
placeholder: Constants::SiteConfig::DETAILS[:staff_user_id][:placeholder] %>
</div>
<div class="crayons-field">
<%= admin_config_label :experience_low %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:experience_low][:description] %>
<%= f.text_field :experience_low,
class: "crayons-texfield",
value: SiteConfig.experience_low,
placeholder: Constants::SiteConfig::DETAILS[:experience_low][:placeholder] %>
</div>
<div class="crayons-field">
<%= admin_config_label :experience_high %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:experience_high][:description] %>
<%= f.text_field :experience_high,
class: "crayons-texfield",
value: SiteConfig.experience_high,
placeholder: Constants::SiteConfig::DETAILS[:experience_high][:placeholder] %>
value: Settings::Community.staff_user_id,
placeholder: Constants::Settings::Community::DETAILS[:staff_user_id][:placeholder] %>
</div>
<%= render "form_submission", f: f %>
</fieldset>

View file

@ -7,7 +7,7 @@ xml.rss version: "2.0" do
xml.channel do
xml.title user ? user.name : community_name
xml.author user ? user.name : community_name
xml.description user ? user.summary : SiteConfig.community_description
xml.description user ? user.summary : Settings::Community.community_description
xml.link user ? app_url(user.path) : app_url
xml.language "en"
if user

View file

@ -6,19 +6,19 @@
) %>
<link rel="canonical" href="<%= app_url(request.path) %>" />
<meta name="description" content="<%= SiteConfig.community_description %>">
<meta name="description" content="<%= Settings::Community.community_description %>">
<%= meta_keywords_default %>
<meta property="og:type" content="website" />
<meta property="og:url" content="<%= app_url(request.path) %>" />
<meta property="og:title" content="<%= title_with_timeframe(page_title: community_name, timeframe: params[:timeframe]) %>" />
<meta property="og:image" content="<%= SiteConfig.main_social_image %>">
<meta property="og:description" content="<%= SiteConfig.community_description %>" />
<meta property="og:description" content="<%= Settings::Community.community_description %>" />
<meta property="og:site_name" content="<%= community_name %>" />
<meta name="twitter:site" content="@<%= SiteConfig.social_media_handles["twitter"] %>">
<meta name="twitter:title" content="<%= title_with_timeframe(page_title: community_name, timeframe: params[:timeframe]) %>">
<meta name="twitter:description" content="<%= SiteConfig.community_description %>">
<meta name="twitter:description" content="<%= Settings::Community.community_description %>">
<meta name="twitter:image:src" content="<%= SiteConfig.main_social_image %>">
<meta name="twitter:card" content="summary_large_image">
<%= auto_discovery_link_tag(:rss, app_url("feed"), title: "#{community_name} RSS Feed") %>

View file

@ -51,7 +51,7 @@
<button value="10" name="rating_vote[rating]" class="level-rating-button <%= "selected" if @rating_vote&.rating.to_d == 10.0.to_d %>">10</button>
</span>
<p>Who is this post most relevant for?</p>
<p><span class="scale-pod">👈 <%= SiteConfig.experience_low %></span><span class="scale-pod"><%= SiteConfig.experience_high %> 👉</span></p>
<p><span class="scale-pod">👈 Beginner</span><span class="scale-pod">Expert 👉</span></p>
<% end %>
</div>
</div>

View file

@ -1,17 +1,17 @@
<% title "Search Results" %>
<link rel="canonical" href="<%= app_url(search_path) %>" />
<meta name="description" content="<%= SiteConfig.community_description %>">
<meta name="description" content="<%= Settings::Community.community_description %>">
<%= meta_keywords_default %>
<meta property="og:type" content="website" />
<meta property="og:url" content="<%= community_name %> => Search Results" />
<meta property="og:title" content="<%= community_name %>" />
<meta property="og:image" content="<%= SiteConfig.main_social_image %>">
<meta property="og:description" content="<%= SiteConfig.community_description %>">
<meta property="og:description" content="<%= Settings::Community.community_description %>">
<meta property="og:site_name" content="<%= community_name %>" />
<meta name="twitter:site" content="@<%= SiteConfig.social_media_handles["twitter"] %>">
<meta name="twitter:title" content="<%= community_name %> => Search Results">
<meta name="twitter:description" content="<%= SiteConfig.community_description %>">
<meta name="twitter:description" content="<%= Settings::Community.community_description %>">
<meta name="twitter:image:src" content="<%= SiteConfig.main_social_image %>">
<meta name="twitter:card" content="summary_large_image">

View file

@ -4,8 +4,8 @@
<div class="dashboard-container analytics-container crayons-layout" id="user-dashboard">
<div class="crayons-card p-3 mt-5">
<h1 class="fs-4xl fw-medium">Analytics Dashboard for <%= @user_or_org.name %></h1>
<p>Welcome to the Analytics Dashboard, the home of in-depth user metrics so that authors can make data-driven decisions about the <%= SiteConfig.community_member_label %> ecosystem.</p>
<p>This dashboard highlights deep insights especially relevant to <%= SiteConfig.community_member_label %> relations, authors, and serious bloggers.</p>
<p>Welcome to the Analytics Dashboard, the home of in-depth user metrics so that authors can make data-driven decisions about the <%= Settings::Community.member_label %> ecosystem.</p>
<p>This dashboard highlights deep insights especially relevant to <%= Settings::Community.member_label %> relations, authors, and serious bloggers.</p>
</div>
<%= render "shared/stats" %>

View file

@ -17,7 +17,7 @@
<div class="pt-4"><%= render partial: "layouts/social_media" %></div>
</nav>
<hr class="crayons-footer__divider">
<p class="fs-s crayons-footer__description"><a href="/" aria-label="<%= community_name %> Home" class="crayons-link"><%= community_name %></a> <%= SiteConfig.community_description %></p>
<p class="fs-s crayons-footer__description"><a href="/" aria-label="<%= community_name %> Home" class="crayons-link"><%= community_name %></a> <%= Settings::Community.community_description %></p>
<div class="m:-mb-4 crayons-footer__description">
<%# The following copy is necessary for compatibility with the Forem AGPL licence which requires instances to link back to the source. %>
<p class="fs-s">Built on <a href="https://www.forem.com" class="crayons-link" target="_blank" rel="noopener">Forem</a> — the <a href="https://dev.to/t/opensource" target="_blank" rel="noopener" class="crayons-link">open source</a> software that powers <a href="https://dev.to" target="_blank" rel="noopener" class="crayons-link">DEV</a> and other inclusive communities.</p>

View file

@ -2,7 +2,7 @@
<% title "Listings" %>
<link rel="canonical" href="<%= app_url(request.path) %>" />
<meta name="description" content="<%= SiteConfig.community_description %>">
<meta name="description" content="<%= Settings::Community.community_description %>">
<%= meta_keywords_default %>
<meta property="og:type" content="website" />
@ -17,10 +17,10 @@
<meta name="twitter:image:src" content="<%= listing_social_image_url @displayed_listing %>">
<% else %>
<meta property="og:title" content="Listings" />
<meta property="og:description" content="<%= SiteConfig.community_description %>" />
<meta property="og:description" content="<%= Settings::Community.community_description %>" />
<meta property="og:image" content="<%= SiteConfig.main_social_image %>">
<meta name="twitter:title" content="Listings">
<meta name="twitter:description" content="<%= SiteConfig.community_description %>">
<meta name="twitter:description" content="<%= Settings::Community.community_description %>">
<meta name="twitter:image:src" content="<%= SiteConfig.main_social_image %>">
<% end %>
<meta name="twitter:site" content="@<%= SiteConfig.social_media_handles["twitter"] %>">

View file

@ -43,7 +43,7 @@
<% elsif tip_rand == 2 %>
And there's so much more <%= community_name %> goodness to discover.
<% elsif tip_rand == 3 %>
You're a better <%= SiteConfig.community_member_label %> today than you were yesterday.
You're a better <%= Settings::Community.member_label %> today than you were yesterday.
<% elsif tip_rand == 4 %>
<% if @user.articles_count > 0 %>
Can't wait to see <b>your</b> next <%= community_name %> post!

View file

@ -13,7 +13,7 @@
<div id="onboarding-container"
data-community-name="<%= community_name %>"
data-community-description="<%= SiteConfig.community_description %>"
data-community-description="<%= Settings::Community.community_description %>"
data-community-logo="<%= optimized_image_url(safe_logo_url(SiteConfig.secondary_logo_url)) %>"
data-community-background="<%= optimized_image_url(SiteConfig.onboarding_background_image, width: 1680, quality: 75, random_fallback: false) %>">
<%= javascript_packs_with_chunks_tag "Onboarding", defer: true %>

View file

@ -2,7 +2,7 @@
<%= content_for :page_meta do %>
<link rel="canonical" href="<%= app_url(request.path) %>" />
<meta name="description" content="<%= SiteConfig.community_description %>">
<meta name="description" content="<%= Settings::Community.community_description %>">
<%= meta_keywords_default %>
<meta property="og:type" content="website" />

View file

@ -61,6 +61,7 @@ Rails.application.routes.draw do
namespace :settings do
resources :authentications, only: [:create]
resources :campaigns, only: [:create]
resources :communities, only: [:create]
resources :mascots, only: [:create]
resources :rate_limits, only: [:create]
end

View file

@ -0,0 +1,77 @@
describe('Community Content Section', () => {
beforeEach(() => {
cy.testSetup();
cy.fixture('users/adminUser.json').as('user');
cy.get('@user').then((user) => {
cy.loginUser(user);
});
});
describe('community emoji setting', () => {
it('rejects invalid input (no emoji)', () => {
cy.get('@user').then(({ username }) => {
cy.visit('/admin/config');
cy.get('#new_settings_community').as('communitySectionForm');
cy.get('@communitySectionForm').findByText('Community Content').click();
cy.get('@communitySectionForm')
.get('#settings_community_community_emoji')
.clear()
.type('X');
cy.get('@communitySectionForm')
.findByPlaceholderText('Confirmation text')
.type(
`My username is @${username} and this action is 100% safe and appropriate.`,
);
cy.get('@communitySectionForm')
.findByText('Update Site Configuration')
.click();
cy.url().should('contains', '/admin/config');
cy.findByText(
'😭 Validation failed: Community emoji contains non-emoji characters or invalid emoji',
).should('be.visible');
});
});
it('accepts a valid emoji', () => {
cy.get('@user').then(({ username }) => {
cy.visit('/admin/config');
cy.get('#new_settings_community').as('communitySectionForm');
cy.get('@communitySectionForm').findByText('Community Content').click();
cy.get('@communitySectionForm')
.get('#settings_community_community_emoji')
.clear()
.type('🌱');
cy.get('@communitySectionForm')
.findByPlaceholderText('Confirmation text')
.type(
`My username is @${username} and this action is 100% safe and appropriate.`,
);
cy.get('@communitySectionForm')
.findByText('Update Site Configuration')
.click();
cy.url().should('contains', '/admin/config');
cy.findByText('Site configuration was successfully updated.').should(
'be.visible',
);
// Page reloaded so need to get a new reference to the form.
cy.get('#new_settings_community').as('communitySectionForm');
cy.get('#settings_community_community_emoji').should(
'have.value',
'🌱',
);
});
});
});
});

View file

@ -0,0 +1,16 @@
class CreateSettingsCommunities < ActiveRecord::Migration[6.1]
def self.up
create_table :settings_communities do |t|
t.string :var, null: false
t.text :value, null: true
t.timestamps
end
add_index :settings_communities, :var, unique: true
end
def self.down
drop_table :settings_communities
end
end

View file

@ -1082,6 +1082,14 @@ ActiveRecord::Schema.define(version: 2021_04_23_162847) do
t.index ["var"], name: "index_settings_campaigns_on_var", unique: true
end
create_table "settings_communities", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.text "value"
t.string "var", null: false
t.index ["var"], name: "index_settings_communities_on_var", unique: true
end
create_table "settings_mascots", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false

View file

@ -333,7 +333,7 @@ seeder.create_if_none(Broadcast) do
tags: welcome
---
Hey there! Welcome to #{SiteConfig.community_name}!
Hey there! Welcome to #{Settings::Community.community_name}!
Leave a comment below to introduce yourself to the community!
HEREDOC

View file

@ -0,0 +1,23 @@
module DataUpdateScripts
SETTINGS = %w[
community_description
community_emoji
community_name
staff_user_id
tagline
].freeze
class MoveCommunitySettings
def run
return if Settings::Community.any?
SETTINGS.each do |setting|
Settings::Community.public_send("#{setting}=", SiteConfig.public_send(setting))
end
# These two settings have been renamed
Settings::Community.copyright_start_year = SiteConfig.community_copyright_start_year
Settings::Community.member_label = SiteConfig.community_member_label
end
end
end

View file

@ -38,7 +38,7 @@ RSpec.describe ApplicationHelper, type: :helper do
describe "#community_name" do
it "equals to the community name" do
allow(SiteConfig).to receive(:community_name).and_return("SLOAN")
allow(Settings::Community).to receive(:community_name).and_return("SLOAN")
expect(helper.community_name).to eq("SLOAN")
end
end
@ -94,21 +94,21 @@ RSpec.describe ApplicationHelper, type: :helper do
context "when the start year and current year is the same" do
it "returns the current year only" do
allow(SiteConfig).to receive(:community_copyright_start_year).and_return(current_year)
allow(Settings::Community).to receive(:copyright_start_year).and_return(current_year)
expect(helper.copyright_notice).to eq(current_year)
end
end
context "when the start year and current year is different" do
it "returns the start and current year" do
allow(SiteConfig).to receive(:community_copyright_start_year).and_return("2014")
allow(Settings::Community).to receive(:copyright_start_year).and_return("2014")
expect(helper.copyright_notice).to eq("2014 - #{current_year}")
end
end
context "when the start year is blank" do
it "returns the current year" do
allow(SiteConfig).to receive(:community_copyright_start_year).and_return(" ")
allow(Settings::Community).to receive(:copyright_start_year).and_return(" ")
expect(helper.copyright_notice).to eq(current_year)
end
end
@ -203,7 +203,7 @@ RSpec.describe ApplicationHelper, type: :helper do
describe "#community_members_label" do
before do
allow(SiteConfig).to receive(:community_member_label).and_return("hobbyist")
allow(Settings::Community).to receive(:member_label).and_return("hobbyist")
end
it "returns the pluralized community_member_label" do

View file

@ -0,0 +1,24 @@
require "rails_helper"
require Rails.root.join(
"lib/data_update_scripts/20210419063311_move_community_settings.rb",
)
describe DataUpdateScripts::MoveCommunitySettings do
it "migrates renamed settings", :aggregate_failures do
allow(SiteConfig).to receive(:community_copyright_start_year).and_return(2564)
allow(SiteConfig).to receive(:community_member_label).and_return("star")
expect { described_class.new.run }
.to change(Settings::Community, :copyright_start_year).to(2564)
.and change(Settings::Community, :member_label).to("star")
end
it "migrates non-renamed settings" do
allow(SiteConfig).to receive(:staff_user_id).and_return(42)
allow(SiteConfig).to receive(:tagline).and_return("D'oh")
expect { described_class.new.run }
.to change(Settings::Community, :staff_user_id).to(42)
.and change(Settings::Community, :tagline).to("D'oh")
end
end

View file

@ -62,7 +62,7 @@ RSpec.describe Article, type: :model do
end
it "includes staff-user-published articles" do
allow(SiteConfig).to receive(:staff_user_id).and_return(3)
allow(Settings::Community).to receive(:staff_user_id).and_return(3)
user = create(:user, id: 3)
create(:article, user: user, tags: "challenge")
expect(described_class.admin_published_with("challenge").count).to eq(1)

View file

@ -827,7 +827,7 @@ RSpec.describe User, type: :model do
end
it "returns the user if the account exists" do
allow(SiteConfig).to receive(:staff_user_id).and_return(user.id)
allow(Settings::Community).to receive(:staff_user_id).and_return(user.id)
expect(described_class.dev_account).to eq(user)
end

View file

@ -184,7 +184,7 @@ RSpec.configure do |config|
"User-Agent" => "Ruby"
}).to_return(status: 200, body: "", headers: {})
allow(SiteConfig).to receive(:community_description).and_return("Some description")
allow(Settings::Community).to receive(:community_description).and_return("Some description")
allow(SiteConfig).to receive(:public).and_return(true)
allow(SiteConfig).to receive(:waiting_on_first_user).and_return(false)

View file

@ -194,74 +194,78 @@ RSpec.describe "/admin/config", type: :request do
describe "Community Content" do
it "updates the community_description" do
allow(SiteConfig).to receive(:community_description).and_call_original
allow(Settings::Community).to receive(:community_description).and_call_original
description = "Hey hey #{rand(100)}"
post "/admin/config", params: { site_config: { community_description: description },
confirmation: confirmation_message }
expect(SiteConfig.community_description).to eq(description)
post admin_settings_communities_path, params: {
settings_community: { community_description: description },
confirmation: confirmation_message
}
expect(Settings::Community.community_description).to eq(description)
end
it "updates the community_emoji if valid" do
allow(SiteConfig).to receive(:community_emoji).and_call_original
allow(Settings::Community).to receive(:community_emoji).and_call_original
emoji = "🥐"
post "/admin/config", params: { site_config: { community_emoji: emoji },
confirmation: confirmation_message }
expect(SiteConfig.community_emoji).to eq(emoji)
post admin_settings_communities_path, params: {
settings_community: { community_emoji: emoji },
confirmation: confirmation_message
}
expect(Settings::Community.community_emoji).to eq(emoji)
end
it "does not update the community_emoji if invalid" do
allow(SiteConfig).to receive(:community_emoji).and_call_original
Settings::Community.community_emoji = "🥐"
not_an_emoji = "i love croissants"
expect do
post "/admin/config", params: { site_config: { community_emoji: not_an_emoji },
confirmation: confirmation_message }
end.not_to change(SiteConfig, :community_emoji)
post admin_settings_communities_path, params: {
settings_community: { community_emoji: not_an_emoji },
confirmation: confirmation_message
}
end.not_to change(Settings::Community, :community_emoji)
end
it "updates the community_name" do
name_magoo = "Hey hey #{rand(100)}"
post "/admin/config", params: { site_config: { community_name: name_magoo },
confirmation: confirmation_message }
expect(SiteConfig.community_name).to eq(name_magoo)
post admin_settings_communities_path, params: {
settings_community: { community_name: name_magoo },
confirmation: confirmation_message
}
expect(Settings::Community.community_name).to eq(name_magoo)
end
it "updates the community_member_label" do
name = "developer"
post "/admin/config", params: { site_config: { community_member_label: name },
confirmation: confirmation_message }
expect(SiteConfig.community_member_label).to eq(name)
post admin_settings_communities_path, params: {
settings_community: { member_label: name },
confirmation: confirmation_message
}
expect(Settings::Community.member_label).to eq(name)
end
it "updates the community_copyright_start_year" do
it "updates the copyright_start_year" do
year = "2018"
post "/admin/config", params: { site_config: { community_copyright_start_year: year },
confirmation: confirmation_message }
expect(SiteConfig.community_copyright_start_year).to eq(2018)
post admin_settings_communities_path, params: {
settings_community: { copyright_start_year: year },
confirmation: confirmation_message
}
expect(Settings::Community.copyright_start_year).to eq(2018)
end
it "updates the tagline" do
description = "Hey hey #{rand(100)}"
post "/admin/config", params: { site_config: { tagline: description }, confirmation: confirmation_message }
expect(SiteConfig.tagline).to eq(description)
post admin_settings_communities_path, params: {
settings_community: { tagline: description },
confirmation: confirmation_message
}
expect(Settings::Community.tagline).to eq(description)
end
it "updates the staff_user_id" do
post "/admin/config", params: { site_config: { staff_user_id: 22 }, confirmation: confirmation_message }
expect(SiteConfig.staff_user_id).to eq(22)
end
it "updates the experience_low" do
experience_low = "Noobs"
post "/admin/config", params: { site_config: { experience_low: experience_low },
confirmation: confirmation_message }
expect(SiteConfig.experience_low).to eq(experience_low)
end
it "updates the experience_high" do
experience_high = "Advanced Peeps"
post "/admin/config", params: { site_config: { experience_high: experience_high },
confirmation: confirmation_message }
expect(SiteConfig.experience_high).to eq(experience_high)
post admin_settings_communities_path, params: {
settings_community: { staff_user_id: 22 },
confirmation: confirmation_message
}
expect(Settings::Community.staff_user_id).to eq(22)
end
end

View file

@ -149,7 +149,7 @@ RSpec.describe "Moderations", type: :request do
let(:user) { create(:user) }
before do
allow(SiteConfig).to receive(:community_name).and_return("DEV")
allow(Settings::Community).to receive(:community_name).and_return("DEV")
end
context "when user logged in" do

View file

@ -31,7 +31,7 @@ RSpec.describe "Onboardings", type: :request do
sign_in user
get onboarding_url
expect(response.body).to include(SiteConfig.community_description)
expect(response.body).to include(Settings::Community.community_description)
expect(response.body).to include(safe_logo_url(SiteConfig.secondary_logo_url))
expect(response.body).to include(SiteConfig.onboarding_background_image)
end

View file

@ -27,7 +27,7 @@ RSpec.describe "StoriesIndex", type: :request do
end
def renders_proper_description
expect(response.body).to include(SiteConfig.community_description)
expect(response.body).to include(Settings::Community.community_description)
end
def renders_min_read_time

View file

@ -46,7 +46,7 @@ RSpec.describe "StoriesShow", type: :request do
## Title tag
it "renders signed-in title tag for signed-in user" do
allow(SiteConfig).to receive(:community_emoji).and_return("🌱")
allow(Settings::Community).to receive(:community_emoji).and_return("🌱")
sign_in user
get article.path
@ -71,7 +71,7 @@ RSpec.describe "StoriesShow", type: :request do
end
it "does not render title tag with search_optimized_title_preamble if set and not signed in" do
allow(SiteConfig).to receive(:community_emoji).and_return("🌱")
allow(Settings::Community).to receive(:community_emoji).and_return("🌱")
sign_in user
article.update_column(:search_optimized_title_preamble, "Hey this is a test")

View file

@ -5,7 +5,7 @@ RSpec.describe Badges::AwardYearlyClub, type: :service do
before do
stub_const("#{described_class}::YEARS", described_class::YEARS.slice(1, 2, 3))
allow(ApplicationConfig).to receive(:[])
allow(SiteConfig).to receive(:community_copyright_start_year).and_return(3.years.ago.year)
allow(Settings::Community).to receive(:copyright_start_year).and_return(3.years.ago.year)
create(:badge, title: "one-year-club")
create(:badge, title: "two-year-club")
create(:badge, title: "three-year-club")

View file

@ -19,7 +19,7 @@ RSpec.describe Broadcasts::WelcomeNotification::Generator, type: :service do
omniauth_mock_providers_payload
allow(Notification).to receive(:send_welcome_notification).and_call_original
allow(User).to receive(:mascot_account).and_return(mascot_account)
allow(SiteConfig).to receive(:staff_user_id).and_return(mascot_account.id)
allow(Settings::Community).to receive(:staff_user_id).and_return(mascot_account.id)
allow(Settings::Authentication).to receive(:providers).and_return(Authentication::Providers.available)
end

View file

@ -8,7 +8,7 @@ RSpec.describe "Admin manages configuration", type: :system do
visit admin_config_path
end
VerifySetupCompleted::MANDATORY_CONFIGS.each do |option|
VerifySetupCompleted::MANDATORY_CONFIGS.each do |option, _setting_model|
it "marks #{option} as required" do
selector = "label[for='site_config_#{option}']"
expect(first(selector).text).to include("Required")
@ -22,13 +22,13 @@ RSpec.describe "Admin manages configuration", type: :system do
end
it "does show the banner on other pages" do
allow(SiteConfig).to receive(:tagline).and_return(nil)
allow(Settings::Community).to receive(:tagline).and_return(nil)
visit root_path
expect(page).to have_content("Setup not completed yet")
end
it "includes information about missing fields on the config pages" do
allow(SiteConfig).to receive(:tagline).and_return(nil)
allow(Settings::Community).to receive(:tagline).and_return(nil)
allow(SiteConfig).to receive(:suggested_users).and_return(nil)
allow(SiteConfig).to receive(:suggested_tags).and_return(nil)
visit root_path

View file

@ -3,6 +3,6 @@ require "rails_helper"
RSpec.describe "stories/_sign_in_invitation.html.erb", type: :view do
it "has the community member label" do
render
expect(rendered).to have_text(SiteConfig.community_member_label.pluralize)
expect(rendered).to have_text(Settings::Community.member_label.pluralize)
end
end