diff --git a/app/controllers/admin/settings/campaigns_controller.rb b/app/controllers/admin/settings/campaigns_controller.rb
new file mode 100644
index 000000000..aaaa4ce86
--- /dev/null
+++ b/app/controllers/admin/settings/campaigns_controller.rb
@@ -0,0 +1,22 @@
+module Admin
+ module Settings
+ class CampaignsController < Admin::ApplicationController
+ def create
+ result = ::Campaigns::SettingsUpsert.call(settings_params)
+
+ if result.success?
+ 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: "😠#{result.errors.to_sentence}"
+ end
+ end
+
+ def settings_params
+ params
+ .require(:settings_campaign)
+ .permit(*::Settings::Campaign.keys)
+ end
+ end
+ end
+end
diff --git a/app/controllers/sidebars_controller.rb b/app/controllers/sidebars_controller.rb
index 3b9e2fe93..c4ce1b60a 100644
--- a/app/controllers/sidebars_controller.rb
+++ b/app/controllers/sidebars_controller.rb
@@ -11,7 +11,7 @@ class SidebarsController < ApplicationController
def get_latest_campaign_articles
campaign_articles_scope = Article.tagged_with(Campaign.current.featured_tags, any: true)
- .where("published_at > ? AND score > ?", SiteConfig.campaign_articles_expiry_time.weeks.ago, 0)
+ .where("published_at > ? AND score > ?", Settings::Campaign.articles_expiry_time.weeks.ago, 0)
.order(hotness_score: :desc)
requires_approval = Campaign.current.articles_require_approval?
diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb
index 807b6bb4b..07d2f1a13 100644
--- a/app/controllers/stories_controller.rb
+++ b/app/controllers/stories_controller.rb
@@ -63,7 +63,7 @@ class StoriesController < ApplicationController
def get_latest_campaign_articles
campaign_articles_scope = Article.tagged_with(Campaign.current.featured_tags, any: true)
- .where("published_at > ? AND score > ?", SiteConfig.campaign_articles_expiry_time.weeks.ago, 0)
+ .where("published_at > ? AND score > ?", Settings::Campaign.articles_expiry_time.weeks.ago, 0)
.order(hotness_score: :desc)
requires_approval = Campaign.current.articles_require_approval?
diff --git a/app/lib/constants/settings/campaign.rb b/app/lib/constants/settings/campaign.rb
new file mode 100644
index 000000000..bbac9dad8
--- /dev/null
+++ b/app/lib/constants/settings/campaign.rb
@@ -0,0 +1,40 @@
+module Constants
+ module Settings
+ module Campaign
+ DETAILS = {
+ articles_expiry_time: {
+ description: "Sets the expiry time for articles (in weeks) to be displayed in campaign sidebar",
+ placeholder: ""
+ },
+ articles_require_approval: {
+ description: "",
+ placeholder: "Campaign stories show up on sidebar with approval?"
+ },
+ call_to_action: {
+ description: "This text populates the call to action button on the campaign sidebar",
+ placeholder: "Share your project"
+ },
+ featured_tags: {
+ description: "Posts with which tags will be featured in the campaign sidebar (comma separated, letters only)",
+ placeholder: "List of campaign tags: comma separated, letters only e.g. shecoded,theycoded"
+ },
+ hero_html_variant_name: {
+ description: "Hero HtmlVariant name",
+ placeholder: ""
+ },
+ sidebar_enabled: {
+ description: "",
+ placeholder: "Campaign sidebar enabled or not"
+ },
+ sidebar_image: {
+ description: ::Constants::SiteConfig::IMAGE_PLACEHOLDER,
+ placeholder: "Used at the top of the campaign sidebar"
+ },
+ url: {
+ description: "https://url.com/lander",
+ placeholder: "URL campaign sidebar image will link to"
+ }
+ }.freeze
+ end
+ end
+end
diff --git a/app/lib/constants/site_config.rb b/app/lib/constants/site_config.rb
index f3e3e6a86..0a54d8855 100644
--- a/app/lib/constants/site_config.rb
+++ b/app/lib/constants/site_config.rb
@@ -4,38 +4,6 @@ module Constants
SVG_PLACEHOLDER = "".freeze
DETAILS = {
- campaign_articles_require_approval: {
- description: "",
- placeholder: "Campaign stories show up on sidebar with approval?"
- },
- campaign_call_to_action: {
- description: "This text populates the call to action button on the campaign sidebar",
- placeholder: "Share your project"
- },
- campaign_featured_tags: {
- description: "Posts with which tags will be featured in the campaign sidebar (comma separated, letters only)",
- placeholder: "List of campaign tags: comma separated, letters only e.g. tagone,tagtwo"
- },
- campaign_hero_html_variant_name: {
- description: "Hero HtmlVariant name",
- placeholder: ""
- },
- campaign_sidebar_enabled: {
- description: "",
- placeholder: "Campaign sidebar enabled or not"
- },
- campaign_sidebar_image: {
- description: IMAGE_PLACEHOLDER,
- placeholder: "Used at the top of the campaign sidebar"
- },
- campaign_url: {
- description: "https://url.com/lander",
- placeholder: "URL campaign sidebar image will link to"
- },
- campaign_articles_expiry_time: {
- description: "Sets the expiry time for articles (in weeks) to be displayed in campaign sidebar",
- placeholder: ""
- },
community_copyright_start_year: {
description: "Used to mark the year this forem was started.",
placeholder: Time.zone.today.year.to_s
diff --git a/app/models/campaign.rb b/app/models/campaign.rb
index 190097713..d698c5ef1 100644
--- a/app/models/campaign.rb
+++ b/app/models/campaign.rb
@@ -10,17 +10,16 @@ class Campaign
end
METHODS = %w[
- hero_html_variant_name
+ articles_require_approval?
+ call_to_action
featured_tags
+ hero_html_variant_name
sidebar_enabled?
sidebar_image
url
- articles_require_approval?
- call_to_action
].freeze
- # Define delegate methods for SiteConfig
- METHODS.each { |m| define_method(m) { SiteConfig.public_send("campaign_#{m}") } }
+ delegate(*METHODS, to: Settings::Campaign)
def show_in_sidebar?
sidebar_enabled? && sidebar_image.present?
diff --git a/app/models/settings/campaign.rb b/app/models/settings/campaign.rb
new file mode 100644
index 000000000..8b5191caf
--- /dev/null
+++ b/app/models/settings/campaign.rb
@@ -0,0 +1,19 @@
+module Settings
+ class Campaign < RailsSettings::Base
+ self.table_name = :settings_campaigns
+
+ # The configuration is cached, change this if you want to force update
+ # the cache, or call Settings::Campaign.clear_cache
+ cache_prefix { "v1" }
+
+ # Define your fields
+ field :articles_expiry_time, type: :integer, default: 4
+ field :articles_require_approval, type: :boolean, default: 0
+ field :call_to_action, type: :string, default: "Share your project"
+ field :featured_tags, type: :array, default: %w[]
+ field :hero_html_variant_name, type: :string, default: ""
+ field :sidebar_enabled, type: :boolean, default: 0
+ field :sidebar_image, type: :string, default: nil, validates: { url: true }
+ field :url, type: :string, default: nil
+ end
+end
diff --git a/app/models/site_config.rb b/app/models/site_config.rb
index 57d9a873c..efeeb1aac 100644
--- a/app/models/site_config.rb
+++ b/app/models/site_config.rb
@@ -11,8 +11,6 @@ class SiteConfig < RailsSettings::Base
HEX_COLOR_REGEX = /\A#(\h{6}|\h{3})\z/.freeze
LIGHTNING_ICON = File.read(Rails.root.join("app/assets/images/lightning.svg")).freeze
STACK_ICON = File.read(Rails.root.join("app/assets/images/stack.svg")).freeze
- VALID_URL = %r{\A(http|https)://([/|.\w\s-])*.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?\z}.freeze
- URL_MESSAGE = "must be a valid URL".freeze
# Forem Team
# [forem-fix] Remove channel name from SiteConfig
@@ -36,18 +34,19 @@ class SiteConfig < RailsSettings::Base
}
field :authentication_providers, type: :array, default: %w[]
+ # NOTE: @citizen428 The whole block of campaign settings will be removed once
+ # we fully migrated to Settings::Campaign across the fleet.
# Campaign
field :campaign_call_to_action, type: :string, default: "Share your project"
field :campaign_hero_html_variant_name, type: :string, default: ""
field :campaign_featured_tags, type: :array, default: %w[]
field :campaign_sidebar_enabled, type: :boolean, default: 0
field :campaign_sidebar_image, type: :string, default: nil, validates: {
- format: { with: VALID_URL, message: URL_MESSAGE }
+ url: true
}
field :campaign_url, type: :string, default: nil
field :campaign_articles_require_approval, type: :boolean, default: 0
field :campaign_articles_expiry_time, type: :integer, default: 4
-
# Community Content
field :community_name, type: :string, default: ApplicationConfig["COMMUNITY_NAME"] || "New Forem"
field :community_emoji, type: :string, default: "🌱", validates: { emoji_only: true }
@@ -88,18 +87,16 @@ class SiteConfig < RailsSettings::Base
field :main_social_image,
type: :string,
default: proc { URL.local_image("social-media-cover.png") },
- validates: { format: { with: VALID_URL, message: URL_MESSAGE } }
+ validates: { url: true }
field :favicon_url, type: :string, default: proc { URL.local_image("favicon.ico") }
field :logo_png,
type: :string,
default: proc { URL.local_image("icon.png") },
- validates: { format: { with: VALID_URL, message: URL_MESSAGE } }
+ validates: { url: true }
field :logo_svg, type: :string
- field :secondary_logo_url, type: :string, validates: {
- format: { with: VALID_URL, message: URL_MESSAGE }
- }
+ field :secondary_logo_url, type: :string, validates: { url: true }
field :enable_video_upload, type: :boolean, default: false
@@ -108,11 +105,9 @@ class SiteConfig < RailsSettings::Base
field :mascot_image_url,
type: :string,
default: proc { URL.local_image("mascot.png") },
- validates: { format: { with: VALID_URL, message: URL_MESSAGE } }
+ validates: { url: true }
field :mascot_image_description, type: :string, default: "The community mascot"
- field :mascot_footer_image_url, type: :string, validates: {
- format: { with: VALID_URL, message: URL_MESSAGE }
- }
+ field :mascot_footer_image_url, type: :string, validates: { url: true }
field :mascot_footer_image_width, type: :integer, default: 52
field :mascot_footer_image_height, type: :integer, default: 120
@@ -141,9 +136,7 @@ class SiteConfig < RailsSettings::Base
field :mailchimp_incoming_webhook_secret, type: :string, default: ""
# Onboarding
- field :onboarding_background_image, type: :string, validates: {
- format: { with: VALID_URL, message: URL_MESSAGE }
- }
+ field :onboarding_background_image, type: :string, validates: { url: true }
field :suggested_tags, type: :array, default: %w[]
field :suggested_users, type: :array, default: %w[]
field :prefer_manual_suggested_users, type: :boolean, default: false
diff --git a/app/services/campaigns/settings_upsert.rb b/app/services/campaigns/settings_upsert.rb
new file mode 100644
index 000000000..e9c8dc6af
--- /dev/null
+++ b/app/services/campaigns/settings_upsert.rb
@@ -0,0 +1,41 @@
+module Campaigns
+ class SettingsUpsert
+ attr_reader :errors
+
+ def self.call(configs)
+ new(configs).call
+ end
+
+ def initialize(configs)
+ @configs = configs
+ @errors = []
+ end
+
+ def call
+ upsert_configs
+ self
+ end
+
+ def success?
+ @errors.none?
+ end
+
+ private
+
+ # NOTE: @citizen428 - This was adapted from Settings::Upsert. I'll see if
+ # a pattern for refactoring emerges in the future, but for now I'll leave
+ # this as-is.
+ def upsert_configs
+ @configs.each do |key, value|
+ if value.is_a?(Array) && value.any?
+ Settings::Campaign.public_send("#{key}=", value.reject(&:blank?))
+ elsif value.present?
+ Settings::Campaign.public_send("#{key}=", value.strip)
+ end
+ rescue ActiveRecord::RecordInvalid => e
+ @errors << e.message
+ next
+ end
+ end
+ end
+end
diff --git a/app/views/admin/configs/show.html.erb b/app/views/admin/configs/show.html.erb
index 9056d2dbd..200ccc85b 100644
--- a/app/views/admin/configs/show.html.erb
+++ b/app/views/admin/configs/show.html.erb
@@ -363,7 +363,7 @@
<% end %>
- <%= form_for(SiteConfig.new, url: admin_config_path) do |f| %>
+ <%= form_for(Settings::Campaign.new, url: admin_settings_campaigns_path) do |f| %>
<%= render partial: "admin/shared/card_header",
locals: {
@@ -375,76 +375,76 @@