diff --git a/Gemfile b/Gemfile index efc9849b7..3832b2c21 100644 --- a/Gemfile +++ b/Gemfile @@ -50,8 +50,11 @@ gem 'grape', '1.3.0' # gem 'puma_worker_killer' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'backburner' gem 'rack', '>= 2.0.6' gem 'rails', '~> 5.2.0' + +gem 'beanstalkd_view', group: :development # Use sqlite3 as the database for Active Record gem 'sqlite3' # Use SCSS for stylesheets diff --git a/Gemfile.lock b/Gemfile.lock index 833250133..b0332bc7a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,8 +103,19 @@ GEM aws-sigv4 (~> 1.1) aws-sigv4 (1.1.0) aws-eventstream (~> 1.0, >= 1.0.2) + backburner (1.5.0) + beaneater (~> 1.0) + concurrent-ruby (~> 1.0, >= 1.0.1) + dante (> 0.1.5) backports (3.15.0) bcrypt (3.1.13) + beaneater (1.0.0) + beanstalkd_view (2.0.0) + beaneater (~> 1.0.0) + json + sinatra (>= 1.3.0) + sinatra-contrib (>= 1.3.0) + vegas (~> 0.1.2) bindex (0.8.1) builder (3.2.3) byebug (11.0.1) @@ -148,6 +159,7 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.5) + dante (0.2.0) devise (4.7.2) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -291,6 +303,8 @@ GEM rack-cors (0.4.1) rack-mini-profiler (1.1.0) rack (>= 1.2.0) + rack-protection (2.0.8.1) + rack rack-test (1.1.0) rack (>= 1.0, < 3) rails (5.2.3) @@ -368,6 +382,18 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) + sinatra (2.0.8.1) + mustermann (~> 1.0) + rack (~> 2.0) + rack-protection (= 2.0.8.1) + tilt (~> 2.0) + sinatra-contrib (2.0.8.1) + backports (>= 2.8.2) + multi_json + mustermann (~> 1.0) + rack-protection (= 2.0.8.1) + sinatra (= 2.0.8.1) + tilt (~> 2.0) spring (2.1.0) sprockets (4.0.0) concurrent-ruby (~> 1.0) @@ -405,6 +431,8 @@ GEM uglifier (4.2.0) execjs (>= 0.3.0, < 3) unicode-display_width (1.6.0) + vegas (0.1.11) + rack (>= 1.0.0) warden (1.2.8) rack (>= 2.0.6) web-console (3.7.0) @@ -436,6 +464,8 @@ DEPENDENCIES audited (~> 4.4) awesome_print aws-sdk-s3 + backburner + beanstalkd_view blazer (= 2.2.1.charcoal)! byebug capistrano diff --git a/Procfile b/Procfile index ed24ee4cf..d080429e9 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,5 @@ -web: bundle exec rails s +web: bundle exec bin/rails s webpacker: ./bin/webpack-dev-server redis: ./redis-git/src/redis-server --port 6378 --loadmodule ./zhregex/module.so --dbfilename dump.rdb +beanstalkd: beanstalkd -l 127.0.0.1 -V +backburner: bundle exec rake backburner:work QUEUE=default,graphql_queries diff --git a/Rakefile b/Rakefile index da4efd575..fa0d905d9 100644 --- a/Rakefile +++ b/Rakefile @@ -4,5 +4,7 @@ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path('../config/application', __FILE__) +require 'backburner/tasks' +require_relative 'config/initializers/backburner.rb' Rails.application.load_tasks diff --git a/app/channels/topbar_channel.rb b/app/channels/topbar_channel.rb index 9a917d975..c7a1bff8f 100644 --- a/app/channels/topbar_channel.rb +++ b/app/channels/topbar_channel.rb @@ -4,9 +4,7 @@ class TopbarChannel < ApplicationCable::Channel def subscribed stream_from 'topbar' - Thread.new do - ActionCable.server.broadcast 'topbar', commit: CurrentCommit - end + ActionCable.server.broadcast 'topbar', commit: CurrentCommit end def unsubscribed diff --git a/app/controllers/authentication_controller.rb b/app/controllers/authentication_controller.rb index 6e041a255..e5f2edf82 100644 --- a/app/controllers/authentication_controller.rb +++ b/app/controllers/authentication_controller.rb @@ -27,11 +27,8 @@ def redirect_target ActiveRecord::Base.logger = old_logger if current_user.write_authenticated - u = current_user - Thread.new do - # Do this in the background to keep the page load fast. - u.update_moderator_sites - end + # Do this in the background to keep the page load fast. + UpdateModeratorSitesJob.perform_later(current_user.id) end flash[:success] = 'Successfully registered token' @@ -74,11 +71,8 @@ def login_redirect_target user.save! - Thread.new do - # Do this in the background to keep the page load fast. - user.update_chat_ids - user.save! - end + # Do this in the background to keep the page load fast. + UpdateChatIdsJob.perform_later(user.id) flash[:success] = "New account created for #{user.username}. Have fun!" end diff --git a/app/controllers/flag_conditions_controller.rb b/app/controllers/flag_conditions_controller.rb index 66300e82c..77b2a0e4e 100644 --- a/app/controllers/flag_conditions_controller.rb +++ b/app/controllers/flag_conditions_controller.rb @@ -131,9 +131,7 @@ def user_overview def validate_user @user = User.find params[:user] - Thread.new do - FlagCondition.validate_for_user @user, current_user - end + ValidateFlagConditionForUserJob.perform_later(@user.id, current_user.id) flash[:info] = 'Validation launched in background.' redirect_back fallback_location: root_path end diff --git a/app/controllers/flag_settings_controller.rb b/app/controllers/flag_settings_controller.rb index e40e3ec22..0667e589d 100644 --- a/app/controllers/flag_settings_controller.rb +++ b/app/controllers/flag_settings_controller.rb @@ -55,9 +55,7 @@ def update # we want to re-validate all existing FlagConditions # and disable them if they aren't in compliance with the # new settings - Thread.new do - FlagCondition.revalidate_all - end + RevalidateFlagConditionsJob.perform_later end format.html { redirect_to flag_settings_path, notice: 'Flag setting was successfully updated.' } diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 385c71922..35964fb32 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -2,7 +2,6 @@ class GraphqlController < ApplicationController skip_before_action :verify_authenticity_token, only: [:execute] - before_action :verify_authorization def execute variables = ensure_hash(params[:variables]) @@ -10,16 +9,59 @@ def execute operation_name = params[:operationName] context = { # Query context goes here, for example: - current_user: current_user + # current_user: current_user } query_params = { variables: variables, context: context, operation_name: operation_name } if user_signed_in? && current_user.has_role?(:core) # query_params.merge!({max_depth: 8, max_complexity:20}) end - @results = MetasmokeSchema.execute(query, **query_params) - respond_to do |format| - format.json { render json: @results } - format.html { @results = JSON.pretty_generate(@results.to_hash) } + + api_key = APIKey.find_by(key: params[:key]) + if (user_signed_in? && current_user.has_role?(:core)) || (!api_key.nil? && api_key.api_tokens.exists?(token: params[:token])) + @results = MetasmokeSchema.execute(query, **query_params) + respond_to do |format| + format.json { render json: @results } + format.html { @results = JSON.pretty_generate(@results.to_hash) } + end + elsif user_signed_in? || !api_key.nil? + @job_id = QueueGraphqlQueryJob.perform_later(query, **query_params).job_id + respond_to do |format| + format.json { render json: { job_id: @job_id } } + format.html { redirect_to view_graphql_job_path(job_id: @job_id) } + end + else + return head :forbidden + end + end + + def view_job + k = "graphql_queries/#{params[:job_id]}" + if redis.sismember 'pending_graphql_queries', params[:job_id] + respond_to do |format| + format.json { render json: { complete: false, pending: true } } + format.html { render action: :pending_job } + end + elsif redis.exists k + v = redis.get k + time_remaining = redis.ttl(k).to_i + @time_elapsed = 600 - time_remaining + if v == '' + respond_to do |format| + format.json { render json: { complete: false, time_elapsed: @time_elapsed } } + format.html { render action: :pending_job } + end + else + @results = JSON.parse(v) + respond_to do |format| + format.json { render json: @results } + format.html do + @results = JSON.pretty_generate(@results.to_hash) + render action: :execute + end + end + end + else + return head 404 end end @@ -27,9 +69,10 @@ def query; end private - def verify_authorization - return head 403 unless !APIKey.find_by(key: params[:key]).nil? || (user_signed_in? && current_user.has_role?(:core)) - end + # def valid_api_token + # key = APIKey.find_by(key: params[:key]) + # !key.nil? && key.api_tokens.exists?(token: params[:token]) + # end # Handle form data, JSON body, or a blank value def ensure_hash(ambiguous_param) diff --git a/app/controllers/review_queues_controller.rb b/app/controllers/review_queues_controller.rb index 1b6145ded..88da56ece 100644 --- a/app/controllers/review_queues_controller.rb +++ b/app/controllers/review_queues_controller.rb @@ -83,11 +83,7 @@ def reviews end def recheck_items - Thread.new do - @queue.items.includes(:reviewable).each do |i| - i.update(completed: true) if i.reviewable.should_dq?(@queue) - end - end + ReviewQueueRecheckItemsJob.perform_later(@queue.id) flash[:info] = 'Checking started in background.' redirect_back fallback_location: review_queues_path end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 2aeb49872..7ffc437db 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,6 +1,37 @@ # frozen_string_literal: true class SearchController < ApplicationController + def new_search; end + + def create_search + ops = %i[title body why username].map do |s| + SearchHelper.parse_search_params(params, s, current_user) + end.flatten + job_id = SearchJob.perform_later(ops, params.permit(VALID_SEARCH_PARAMS)).job_id + redirect_to search_pending_path(job_id: job_id) + end + + def search_pending + return head :not_found unless redis.exists "search_jobs/#{params[:job_id]}/sid" + @sid = redis.get "search_jobs/#{params[:job_id]}/sid" + @sid_ttl = redis.ttl("search_jobs/#{params[:job_id]}/sid").to_i + @be_page = redis.get("search_jobs/#{params[:job_id]}/be_page").to_i + return unless redis.exists "searches/#{@sid}/results/#{@be_page}" + per_page = 100 + redirect_to search_results_path(sid: @sid, page: (@be_page * SearchJob.search_page_length) / per_page, per_page: per_page) + end + + def search_results + @search_params = JSON.parse(redis.get("searches/#{params[:sid]}/params")).symbolize_keys + @result_count = redis.get("searches/#{params[:sid].to_i}/result_count").to_i + @results = search_get_page(params[:page].to_i, params[:per_page].to_i, sid: params[:sid].to_i) + redirect_to search_pending_path(job_id: @results) if @results.is_a? String + total_pages = (@result_count / params[:per_page].to_i) + current_page = params[:page].to_i + @results.define_singleton_method(:total_pages) { total_pages } + @results.define_singleton_method(:current_page) { current_page } + end + def index # This might be ugly, but it's better than the alternative. # @@ -319,4 +350,29 @@ def index_fast end redis.del final_key end + + private + + def search_get_page(page_num, per_page = 100, **hsh) + total_offset = per_page * page_num + per_page_real = [per_page, SearchJob.search_page_length].min + be_page_offset = total_offset % SearchJob.search_page_length + be_page = (total_offset / SearchJob.search_page_length).floor + if redis.exists "searches/#{hsh[:sid]}/results/#{be_page}" + @counts_by_accuracy_group = JSON.parse(redis.get("searches/#{hsh[:sid]}/counts_by_accuracy_group")).symbolize_keys + @counts_by_feedback = JSON.parse(redis.get("searches/#{hsh[:sid]}/counts_by_feedback")).symbolize_keys + return Post.where(id: redis.get("searches/#{hsh[:sid]}/results/#{be_page}").unpack('I!*')) + .order(created_at: :desc) + .offset(be_page_offset) + .limit(per_page_real) + .includes_for_post_row + elsif hsh.key?(:sid) + unless redis.set("searches/#{hsh[:sid]}/results/#{be_page}/job_id", '', nx: true) + return redis.get("searches/#{hsh[:sid]}/results/#{be_page}/job_id") + end + job_id = SearchExtendJob.perform_later(hsh[:sid], be_page).job_id + redis.set "searches/#{hsh[:sid]}/results/#{be_page}/job_id", job_id + return job_id + end + end end diff --git a/app/controllers/status_controller.rb b/app/controllers/status_controller.rb index a85d4020a..b46ba9110 100644 --- a/app/controllers/status_controller.rb +++ b/app/controllers/status_controller.rb @@ -25,12 +25,10 @@ def status_update @smoke_detector.save! - Thread.new do - ActionCable.server.broadcast 'status', status_channel_data - ActionCable.server.broadcast 'status_blacklist_manager', status_channel_data.merge(failover_link: failover_link) - ActionCable.server.broadcast 'topbar', last_ping: @smoke_detector.last_ping.to_f - ActionCable.server.broadcast 'smokey_pings', smokey: @smoke_detector.as_json - end + ActionCable.server.broadcast 'status', status_channel_data + ActionCable.server.broadcast 'status_blacklist_manager', status_channel_data.merge(failover_link: failover_link) + ActionCable.server.broadcast 'topbar', last_ping: @smoke_detector.last_ping.to_f + ActionCable.server.broadcast 'smokey_pings', smokey: @smoke_detector.as_json respond_to do |format| format.json do diff --git a/app/jobs/queue_graphql_query_job.rb b/app/jobs/queue_graphql_query_job.rb new file mode 100644 index 000000000..d1a62ed6a --- /dev/null +++ b/app/jobs/queue_graphql_query_job.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class QueueGraphqlQueryJob < ApplicationJob + queue_as :graphql_queries + + def perform(query, **query_params) + puts 'PERF' + @results = MetasmokeSchema.execute(query, **query_params) + end + + before_enqueue do + redis.sadd 'pending_graphql_queries', job_id + end + + before_perform do + redis.set "graphql_queries/#{job_id}", '', ex: 600 + redis.srem 'pending_graphql_queries', job_id + end + + after_perform do + redis.set "graphql_queries/#{job_id}", JSON.generate(@results), ex: 300 + end +end diff --git a/app/jobs/revalidate_flag_conditions_job.rb b/app/jobs/revalidate_flag_conditions_job.rb new file mode 100644 index 000000000..346027b11 --- /dev/null +++ b/app/jobs/revalidate_flag_conditions_job.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class RevalidateFlagConditionsJob < ApplicationJob + queue_as :default + + def perform + FlagCondition.where(flags_enabled: true).find_each do |fc| + unless fc.validate + failures = fc.errors.full_messages + fc.flags_enabled = false + fc.save(validate: false) + ActionCable.server.broadcast 'smokedetector_messages', + message: "@#{fc.user&.username&.tr(' ', '')} " \ + "Your flag condition was disabled: #{failures.join(',')}" + end + end + end +end diff --git a/app/jobs/review_queue_recheck_items_job.rb b/app/jobs/review_queue_recheck_items_job.rb new file mode 100644 index 000000000..14c2c3458 --- /dev/null +++ b/app/jobs/review_queue_recheck_items_job.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class ReviewQueueRecheckItemsJob < ApplicationJob + queue_as :default + + def perform(queue_id) + queue = Queue.find(queue_id) + queue.items.includes(:reviewable).each do |i| + i.update(completed: true) if i.reviewable.should_dq?(queue) + end + end +end diff --git a/app/jobs/search_extend_job.rb b/app/jobs/search_extend_job.rb new file mode 100644 index 000000000..2f1f0c391 --- /dev/null +++ b/app/jobs/search_extend_job.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "#{Rails.root}/app/jobs/search_helpers.rb" + +class SearchExtendJob < ApplicationJob + queue_as :searches + include Backburner::Queue + queue_respond_timeout SEARCH_JOB_TIMEOUT + + include SearchJobQueryBuilder + + before_enqueue do |job| + sid, be_page = job.arguments + job_id = job.job_id + redis.set "search_jobs/#{job_id}/sid", sid + redis.set "search_jobs/#{job_id}/be_page", be_page + end + + def perform(sid, be_page) + redis.expire "search_jobs/#{job_id}/sid", SEARCH_JOB_TIMEOUT + redis.expire "search_jobs/#{job_id}/be_page", SEARCH_JOB_TIMEOUT + wrapped_params = JSON.parse(redis.get("searches/#{sid}/params")).symbolize_keys + ops = redis.lrange "searches/#{sid}/ops", 0, -1 + created_at = redis.get "searches/#{sid}/created_at" + query = build_query(ops, wrapped_params) + query = query.where('posts.created_at < ?', Time.at(created_at.to_i)) + search_results = query.offset(SEARCH_PAGE_LENGTH * be_page).limit(SEARCH_PAGE_LENGTH).select(:id).map(&:id).pack('I!*') + redis.set "searches/#{sid}/results/#{be_page}", search_results + redis.expire "searches/#{sid}/results/0", SEACH_PAGE_EXPIRATION + end + + after_perform do |job| + sid, be_page = job.arguments + redis.del "searches/#{sid}/results/#{be_page}/job_id" + end +end diff --git a/app/jobs/search_helpers.rb b/app/jobs/search_helpers.rb new file mode 100644 index 000000000..14eb9de02 --- /dev/null +++ b/app/jobs/search_helpers.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +SEARCH_PAGE_LENGTH = 10_000 +VALID_SEARCH_PARAMS = %i[ + post_type_include_unmatched autoflagged post_type user_rep_direction + site edited or_search option user_reputation reason feedback + title title_is_regex title_is_inverse_regex + body body_is_like body_is_regex body_is_inverse_regex + why why_is_regex why_is_inverse_regex + username username_is_regex username_is_inverse_regex +].freeze +SEARCH_JOB_TIMEOUT = (60 * 60 * 3) +SEACH_PAGE_EXPIRATION = 1.hour +module SearchJobQueryBuilder + def build_query(ops, wrapped_params) + title, title_operation, + body, body_operation, + why, why_operation, + username, username_operation = ops + + user_reputation = wrapped_params[:user_reputation].to_i || 0 + + case wrapped_params[:feedback] + when /true/ + feedback = :is_tp + when /false/ + feedback = :is_fp + when /NAA/ + feedback = :is_naa + end + + results = if wrapped_params[:reason].present? + Reason.find(wrapped_params[:reason]).posts + else + Post.all + end + + search_string = [] + search_params = {} + [[:username, username, username_operation], [:title, title, title_operation], + [:why, why, why_operation]].each do |si| + if si[1].present? && si[1] != '%%' + search_string << "IFNULL(`posts`.`#{si[0]}`, '') #{si[2]} :#{si[0]}" + search_params[si[0]] = si[1] + end + end + + if body.present? + if ['LIKE', 'NOT LIKE'].include?(body_operation) && wrapped_params[:body_is_like] != '1' + # If the operation would be LIKE, hijack it and use our fulltext index for a search instead. + # UNLESS... params[:body_is_like] is set, in which case the user has explicitly specified a LIKE query. + results = results.match_search(body, with_search_score: false, posts: :body) + else + if wrapped_params[:body_is_like] == '1' && !user_signed_in? + flash[:warning] = 'Unregistered users cannot use LIKE searches on the body field. Please sign in.' + redirect_to(search_path) && return + end + # Otherwise, it's REGEX or NOT REGEX, which fulltext won't do - fall back on search_string and params + search_string << "IFNULL(`posts`.`body`, '') #{body_operation} :body" + search_params[:body] = body.present? ? body : '%%' + end + end + + results = results.where(search_string.join(wrapped_params[:or_search].present? ? ' OR ' : ' AND '), **search_params) + + # results = results.includes(:reasons).includes(:feedbacks) if wrapped_params[:option].nil? + results = results.joins(:reasons).joins(:feedbacks) if wrapped_params[:option].nil? + + if feedback.present? + results = results.where(feedback => true) + elsif wrapped_params[:feedback] == 'conflicted' + results = results.where(is_tp: true, is_fp: true) + end + + results = case wrapped_params[:user_rep_direction] + when '>=' + if user_reputation > 0 + results.where('IFNULL(user_reputation, 0) >= :rep', rep: user_reputation) + end + when '==' + results.where('IFNULL(user_reputation, 0) = :rep', rep: user_reputation) + when '<=' + results.where('IFNULL(user_reputation, 0) <= :rep', rep: user_reputation) + else + results + end + + results = results.where(site_id: wrapped_params[:site]) if wrapped_params[:site].present? + + results = results.where('revision_count > 1') if wrapped_params[:edited].present? + + # results = results.includes(feedbacks: [:user]) + + case wrapped_params[:autoflagged].try(:downcase) + when 'yes' + results = results.autoflagged + when 'no' + results = results.not_autoflagged + end + + post_type = case wrapped_params[:post_type].try(:downcase).try(:[], 0) + when 'q' + 'questions' + when 'a' + 'a' + end + + if post_type.present? + unmatched = results.where.not("link LIKE '%/questions/%' OR link LIKE '%/a/%'") + results = if wrapped_params[:post_type_include_unmatched] + results.where('link like ?', "%/#{post_type}/%").or(unmatched) + else + results.where('link like ?', "%/#{post_type}/%") + end + end + + results.distinct.order(Arel.sql('`posts`.`created_at` DESC')) + end +end diff --git a/app/jobs/search_job.rb b/app/jobs/search_job.rb new file mode 100644 index 000000000..4c5d1d705 --- /dev/null +++ b/app/jobs/search_job.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "#{Rails.root}/app/jobs/search_helpers.rb" + +class SearchJob < ApplicationJob + queue_as :searches + include Backburner::Queue + queue_respond_timeout SEARCH_JOB_TIMEOUT + + include SearchJobQueryBuilder + + def self.search_page_length + SEARCH_PAGE_LENGTH + end + + def self.search_job_timeout + SEARCH_JOB_TIMEOUT + end + + attr_accessor :search_id + + before_enqueue do |job| + ops, params = job.arguments + job_id = job.job_id + wrapped_params = VALID_SEARCH_PARAMS.map { |k| [k, params[k]] }.to_h + sid = redis.incr 'search_counter' + redis.set "search_jobs/#{job_id}/sid", sid + redis.set "search_jobs/#{job_id}/be_page", 0 + redis.set "searches/#{sid}/params", JSON.generate(wrapped_params) + redis.rpush "searches/#{sid}/ops", ops + end + + def perform(ops, params) + # sid = @sid + redis.expire "search_jobs/#{job_id}/sid", SEARCH_JOB_TIMEOUT + redis.expire "search_jobs/#{job_id}/be_page", SEARCH_JOB_TIMEOUT + sid = redis.get("search_jobs/#{job_id}/sid").to_i + wrapped_params = VALID_SEARCH_PARAMS.map { |k| [k, params[k]] }.to_h + created_at = Time.now.to_i + redis.set "searches/#{sid}/created_at", created_at + + results = build_query(ops, wrapped_params) + results = results.where('posts.created_at < ?', Time.at(created_at)) + + counts_by_accuracy_group = results.group(:is_tp, :is_fp, :is_naa).count + counts_by_feedback = %i[is_tp is_fp is_naa].each_with_index.map do |symbol, i| + [symbol, counts_by_accuracy_group.select { |k, _v| k[i] }.values.sum] + end.to_h + redis.set "searches/#{sid}/result_count", results.count + redis.set "searches/#{sid}/counts_by_accuracy_group", JSON.generate(counts_by_accuracy_group) + redis.set "searches/#{sid}/counts_by_feedback", JSON.generate(counts_by_feedback) + redis.set "searches/#{sid}/results/0", results.limit(SEARCH_PAGE_LENGTH).map(&:id).pack('I!*') + redis.expire "searches/#{sid}/results/0", SEACH_PAGE_EXPIRATION + end +end diff --git a/app/jobs/update_chat_ids_job.rb b/app/jobs/update_chat_ids_job.rb new file mode 100644 index 000000000..e8b4ca7d4 --- /dev/null +++ b/app/jobs/update_chat_ids_job.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class UpdateChatIdsJob < ApplicationJob + queue_as :default + + def perform(user_id) + u = User.find(user_id) + u.update_chat_ids + u.save! + end +end diff --git a/app/jobs/update_moderator_sites_job.rb b/app/jobs/update_moderator_sites_job.rb new file mode 100644 index 000000000..809f0fb21 --- /dev/null +++ b/app/jobs/update_moderator_sites_job.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class UpdateModeratorSitesJob < ApplicationJob + queue_as :default + + def perform(user_id) + User.find(user_id).update_moderator_sites + end +end diff --git a/app/jobs/validate_flag_condition_for_user_job.rb b/app/jobs/validate_flag_condition_for_user_job.rb index a0d280a6f..2616637b4 100644 --- a/app/jobs/validate_flag_condition_for_user_job.rb +++ b/app/jobs/validate_flag_condition_for_user_job.rb @@ -3,8 +3,8 @@ class ValidateFlagConditionForUserJob < ApplicationJob queue_as :default - def perform(user_id) - FlagCondition.validate_for_user(User.find(user_id), User.find(-1)) + def perform(user_id, other_user_id = -1) + FlagCondition.validate_for_user(User.find(user_id), User.find(other_user_id)) # Do something later end end diff --git a/app/models/flag_condition.rb b/app/models/flag_condition.rb index 3254e4dfb..6e31e9e50 100644 --- a/app/models/flag_condition.rb +++ b/app/models/flag_condition.rb @@ -30,19 +30,6 @@ def accuracy true_positive_count.to_f * 100 / post_feedback_results.count.to_f end - def self.revalidate_all - FlagCondition.where(flags_enabled: true).find_each do |fc| - unless fc.validate - failures = fc.errors.full_messages - fc.flags_enabled = false - fc.save(validate: false) - ActionCable.server.broadcast 'smokedetector_messages', - message: "@#{fc.user&.username&.tr(' ', '')} " \ - "Your flag condition was disabled: #{failures.join(',')}" - end - end - end - def self.overall_accuracy(user) query = File.read(Rails.root.join('lib/queries/overall_accuracy.sql')) sanitized = ActiveRecord::Base.sanitize_sql([query, user_id: user.id]) diff --git a/app/views/graphql/pending_job.html.erb b/app/views/graphql/pending_job.html.erb new file mode 100644 index 000000000..a2dd77ba2 --- /dev/null +++ b/app/views/graphql/pending_job.html.erb @@ -0,0 +1,7 @@ +<% if @time_elapsed.nil? %> +
Your job is in the queue. Keep refreshing this page to see once it starts running. +<% else %> +
Your job is still pending, it has been running for <%= @time_elapsed %> seconds and will terminate after 600 seconds
+Reload the page to see if your job has completed. If you get a 404, it has expired.
+<% end %> +After your job has completed, it will be available for 5 minutes
diff --git a/app/views/search/new_search.html.erb b/app/views/search/new_search.html.erb new file mode 100644 index 000000000..4c7d6ec76 --- /dev/null +++ b/app/views/search/new_search.html.erb @@ -0,0 +1,93 @@ +<% title "Search" %> + +<%= form_tag (params[:option] == 'graphs' ? create_search_path(anchor: "graphs") : create_search_path), method: "get" do |f| %> +Your search is pending. Please keep reloading and eventually it'll work :)
+<% else %> +
Your search has been running for <%= SearchJob.search_job_timeout - @sid_ttl %> seconds...
+<% end %> diff --git a/app/views/search/search_results.html.erb b/app/views/search/search_results.html.erb new file mode 100644 index 000000000..b83a348bc --- /dev/null +++ b/app/views/search/search_results.html.erb @@ -0,0 +1,125 @@ + + + +<% if params[:option].nil? %> ++ <%= link_to "JSON (still subject to paging limits)", search_path(params: request.query_parameters, format: :json) %> +
+