diff --git a/Gemfile b/Gemfile index 50287ffa77abd681722ce1d7cfafdec1c2d05ea9..f5bd290e37d02024b04504d730efff6ced73455b 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,7 @@ gem 'mysql2', '~> 0.5.4' gem 'puma', '~> 5.6' gem 'rails', '~> 7.0.0' gem 'rails-html-sanitizer', '~> 1.4' -gem 'redis', '~> 5.0' +gem 'redis', '~> 4.8' gem 'rotp', '~> 6.2' gem 'sass-rails', '~> 6.0' gem 'sprockets', '~> 4.1' diff --git a/Gemfile.lock b/Gemfile.lock index 429090e1c67d1c53060b181df8f6d41442a03c23..4bac246fe5d118dfdf86419a81abbaa128a4c627 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -107,7 +107,6 @@ GEM coffee-script-source (1.12.2) commonmarker (0.23.5) concurrent-ruby (1.1.10) - connection_pool (2.2.5) counter_culture (3.2.1) activerecord (>= 4.2) activesupport (>= 4.2) @@ -262,10 +261,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - redis (5.0.4) - redis-client (>= 0.7.4) - redis-client (0.8.0) - connection_pool + redis (4.8.0) regexp_parser (2.5.0) responders (3.0.1) actionpack (>= 5.0) @@ -396,7 +392,7 @@ DEPENDENCIES rails (~> 7.0.0) rails-controller-testing (~> 1.0) rails-html-sanitizer (~> 1.4) - redis (~> 5.0) + redis (~> 4.8) reverse_markdown (~> 2.1) rmagick rotp (~> 6.2) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 68cd945fbe804a805dde618a63eba9e8d2c80fcb..ce8c81040301ea46cc73e07d5a4da8535819618b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -268,7 +268,9 @@ class ApplicationController < ActionController::Base @hot_questions = Rails.cache.fetch('hot_questions', expires_in: 4.hours) do Rack::MiniProfiler.step 'hot_questions: cache miss' do - Post.undeleted.where(last_activity: (Rails.env.development? ? 365 : 7).days.ago..DateTime.now) + Post.undeleted.where(closed: false) + .where(locked: false) + .where(last_activity: (Rails.env.development? ? 365 : 7).days.ago..DateTime.now) .where(post_type_id: [Question.post_type_id, Article.post_type_id]) .joins(:category).where(categories: { use_for_hot_posts: true }) .where('score >= ?', SiteSetting['HotPostsScoreThreshold']) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index f9e0e26c736813820eb11a1492cfcafb1fcf4e96..d3295ea7729f0c1aec73d4ad2b55e99031e2be46 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -99,6 +99,7 @@ class PostsController < ApplicationController end if @post.save + @post.update(last_activity: @post.created_at, last_activity_by: current_user) if @post_type.has_parent? unless @post.user_id == @post.parent.user_id @post.parent.user.create_notification("New response to your post #{@post.parent.title}", diff --git a/app/models/post.rb b/app/models/post.rb index cac04dbc84ad70deb78bd863f15afbe8e07b9058..458b91df77669d4f2798a1c314f407d5a6c880cf 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -265,9 +265,9 @@ class Post < ApplicationRecord def maximum_tags if tags_cache.length > 5 - errors.add(:tags, "can't have more than 5 tags") + errors.add(:base, "Post can't have more than 5 tags.") elsif tags_cache.empty? - errors.add(:tags, 'must have at least one tag') + errors.add(:base, 'Post must have at least one tag.') end end diff --git a/app/models/post_flag_type.rb b/app/models/post_flag_type.rb index 4731ee12bf69f5c046e4b8feedd5563241e210e9..00bed5ce46cf9ccb5a5c751d58bff73d32a61eda 100644 --- a/app/models/post_flag_type.rb +++ b/app/models/post_flag_type.rb @@ -1,6 +1,8 @@ class PostFlagType < ApplicationRecord include CommunityRelated + belongs_to :post_type, optional: true + validates :name, uniqueness: { scope: [:community_id], case_sensitive: false } scope :not_confidential, -> { where(confidential: false) } diff --git a/config/environments/development.rb b/config/environments/development.rb index 06ab403381b00391f412c48da1e5c0f9b07a43f5..3586b8d5134d4a77bb0f3c2d3726bd809e9e480a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -24,7 +24,10 @@ Rails.application.configure do redis_config = YAML.safe_load(processed, permitted_classes: [], permitted_symbols: [], aliases: true)["redis_#{Rails.env}"] config.cache_store = QPixel::NamespacedEnvCache.new( ActiveSupport::Cache::RedisCacheStore.new( - url: "redis://#{redis_config['host']}:#{redis_config['port']}" + **redis_config.deep_symbolize_keys.merge(reconnect_attempts: 3), + error_handler: -> (method:, returning:, exception:) { + Rails.logger.error("Cache error: method=#{method} returning=#{returning} exception=#{exception.message}") + } ) ) diff --git a/config/environments/production.rb b/config/environments/production.rb index f86b8ec2fc68b95c52eee9c337ff352d0151458e..7a7b6d2d4a42de6a8afcc240e99c9e218c257c81 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -58,9 +58,16 @@ Rails.application.configure do # Prepend all log lines with the following tags. config.log_tags = [ :subdomain, :uuid ] - # Use a different cache store in production. + # Set the cache store to the redis that was configured in the database.yml + processed = ERB.new(File.read(Rails.root.join('config', 'database.yml'))).result(binding) + redis_config = YAML.safe_load(processed, permitted_classes: [], permitted_symbols: [], aliases: true)["redis_#{Rails.env}"] config.cache_store = QPixel::NamespacedEnvCache.new( - ActiveSupport::Cache::RedisCacheStore.new(url: 'redis://localhost:6379/1') + ActiveSupport::Cache::RedisCacheStore.new( + **redis_config.deep_symbolize_keys.merge(reconnect_attempts: 3), + error_handler: -> (method:, returning:, exception:) { + Rails.logger.error("Cache error: method=#{method} returning=#{returning} exception=#{exception.message}") + } + ) ) # Use a real queuing backend for Active Job (and separate queues per environment). diff --git a/config/environments/test.rb b/config/environments/test.rb index 4c5ef5309711a0e4c6fb2a5213cebb2c07ac63b9..8bd2cfad8a20dcb3ba63cd1efd3196a7fb39695c 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -28,11 +28,15 @@ Rails.application.configure do config.consider_all_requests_local = true config.action_controller.perform_caching = false + # Set the cache store to the redis that was configured in the database.yml processed = ERB.new(File.read(Rails.root.join('config', 'database.yml'))).result(binding) redis_config = YAML.safe_load(processed, permitted_classes: [], permitted_symbols: [], aliases: true)["redis_#{Rails.env}"] config.cache_store = QPixel::NamespacedEnvCache.new( ActiveSupport::Cache::RedisCacheStore.new( - url: "redis://#{redis_config['host']}:#{redis_config['port']}" + **redis_config.deep_symbolize_keys.merge(reconnect_attempts: 3), + error_handler: -> (method:, returning:, exception:) { + Rails.logger.error("Cache error: method=#{method} returning=#{returning} exception=#{exception.message}") + } ) ) diff --git a/db/seeds.rb b/db/seeds.rb index 9a5c8579049a3d7cf5b7df5145725dde12981335..c116c94c36aaaca729481cf0a52ed2b020d8fff9 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -62,7 +62,27 @@ sorted.each do |f, type| else # otherwise, no need to worry, just create it [seed] - end + end + + # Transform all _id relations into the actual rails objects to pass validations + seeds = seeds.map do |seed| + columns = type.column_names.select { |name| name.match(/^.*_id$/) } + new_seed = seed.deep_symbolize_keys + columns.each do |column| + begin + column_type_name = column.chomp('_id') + column_type = column_type_name.classify.constantize + new_seed = new_seed.except(column.to_sym) + .merge(column_type_name.to_sym => column_type.unscoped.find(seed[column.to_sym])) + rescue StandardError + # Either the type does not exist or the value specified as the id is not valid, ignore. + next + end + end + new_seed + end + + # Actually create the objects and count successes objs = type.create seeds skipped += objs.select { |o| o.errors.any? }.size created += objs.select { |o| !o.errors.any? }.size diff --git a/db/seeds/categories.yml b/db/seeds/categories.yml index 9c4387cd57838894d178b822d780e0f80ceaa093..d8c5a1f2876dbd90c5a2269e1e08e4e376991f50 100644 --- a/db/seeds/categories.yml +++ b/db/seeds/categories.yml @@ -9,6 +9,7 @@ tag_set_id: <%= TagSet.unscoped.where(name: 'Main').first.id %> use_for_hot_posts: true use_for_advertisement: true + license_id: <%= License.unscoped.first.id %> - name: Meta short_wiki: Discussions and feedback about the site itself in Q&A format. @@ -20,4 +21,5 @@ tag_set_id: <%= TagSet.unscoped.where(name: 'Meta').first.id %> use_for_hot_posts: true use_for_advertisement: false - color_code: bluegray \ No newline at end of file + color_code: bluegray + license_id: <%= License.unscoped.first.id %> \ No newline at end of file