From 75e759b1697e5bbd7243146c549e1403c788a857 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Tue, 26 Mar 2024 13:30:06 -0400 Subject: [PATCH 01/26] intial --- .tool-versions | 1 + Gemfile | 2 +- Gemfile.lock | 301 +++++++++--------- config/.tool-versions | 1 + ...desired_outcome_to_recommended_response.rb | 2 +- db/schema.rb | 22 +- 6 files changed, 169 insertions(+), 160 deletions(-) create mode 100644 .tool-versions create mode 100644 config/.tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..7f44804c --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 2.6.3 diff --git a/Gemfile b/Gemfile index a45d00b0..e4e96882 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '3.1.2' +ruby '2.6.3' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0' diff --git a/Gemfile.lock b/Gemfile.lock index c402d17a..2df3151a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,109 +10,110 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (2.3.6) - actioncable (6.1.7.4) - actionpack (= 6.1.7.4) - activesupport (= 6.1.7.4) + actioncable (6.1.7.7) + actionpack (= 6.1.7.7) + activesupport (= 6.1.7.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.4) - actionpack (= 6.1.7.4) - activejob (= 6.1.7.4) - activerecord (= 6.1.7.4) - activestorage (= 6.1.7.4) - activesupport (= 6.1.7.4) + actionmailbox (6.1.7.7) + actionpack (= 6.1.7.7) + activejob (= 6.1.7.7) + activerecord (= 6.1.7.7) + activestorage (= 6.1.7.7) + activesupport (= 6.1.7.7) mail (>= 2.7.1) - actionmailer (6.1.7.4) - actionpack (= 6.1.7.4) - actionview (= 6.1.7.4) - activejob (= 6.1.7.4) - activesupport (= 6.1.7.4) + actionmailer (6.1.7.7) + actionpack (= 6.1.7.7) + actionview (= 6.1.7.7) + activejob (= 6.1.7.7) + activesupport (= 6.1.7.7) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.4) - actionview (= 6.1.7.4) - activesupport (= 6.1.7.4) + actionpack (6.1.7.7) + actionview (= 6.1.7.7) + activesupport (= 6.1.7.7) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.4) - actionpack (= 6.1.7.4) - activerecord (= 6.1.7.4) - activestorage (= 6.1.7.4) - activesupport (= 6.1.7.4) + actiontext (6.1.7.7) + actionpack (= 6.1.7.7) + activerecord (= 6.1.7.7) + activestorage (= 6.1.7.7) + activesupport (= 6.1.7.7) nokogiri (>= 1.8.5) - actionview (6.1.7.4) - activesupport (= 6.1.7.4) + actionview (6.1.7.7) + activesupport (= 6.1.7.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.7.4) - activesupport (= 6.1.7.4) + activejob (6.1.7.7) + activesupport (= 6.1.7.7) globalid (>= 0.3.6) - activemodel (6.1.7.4) - activesupport (= 6.1.7.4) - activerecord (6.1.7.4) - activemodel (= 6.1.7.4) - activesupport (= 6.1.7.4) - activestorage (6.1.7.4) - actionpack (= 6.1.7.4) - activejob (= 6.1.7.4) - activerecord (= 6.1.7.4) - activesupport (= 6.1.7.4) + activemodel (6.1.7.7) + activesupport (= 6.1.7.7) + activerecord (6.1.7.7) + activemodel (= 6.1.7.7) + activesupport (= 6.1.7.7) + activestorage (6.1.7.7) + actionpack (= 6.1.7.7) + activejob (= 6.1.7.7) + activerecord (= 6.1.7.7) + activesupport (= 6.1.7.7) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.4) + activesupport (6.1.7.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.4) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) - ahoy_matey (5.0.2) - activesupport (>= 6.1) - device_detector (>= 1) - safely_block (>= 0.4) + ahoy_matey (4.2.1) + activesupport (>= 5.2) + device_detector + safely_block (>= 0.2.1) aliyun-sdk (0.8.0) nokogiri (~> 1.6) rest-client (~> 2.0) - aws-eventstream (1.2.0) - aws-partitions (1.791.0) - aws-sdk-core (3.178.0) - aws-eventstream (~> 1, >= 1.0.2) + aws-eventstream (1.3.0) + aws-partitions (1.895.0) + aws-sdk-core (3.191.3) + aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.71.0) - aws-sdk-core (~> 3, >= 3.177.0) + aws-sdk-kms (1.77.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.131.0) - aws-sdk-core (~> 3, >= 3.177.0) + aws-sdk-s3 (1.143.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.6.0) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) - bcrypt (3.1.19) + base64 (0.2.0) + bcrypt (3.1.20) bindex (0.8.1) - bootsnap (1.16.0) + bootsnap (1.18.3) msgpack (~> 1.2) builder (3.2.4) byebug (11.1.3) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) connection_pool (2.4.1) crass (1.0.6) - date (3.3.3) + date (3.3.4) declarative (0.0.20) - device_detector (1.1.1) - devise (4.9.2) + device_detector (1.0.7) + devise (4.9.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - devise_invitable (2.0.8) + devise_invitable (2.0.9) actionmailer (>= 5.0) devise (>= 4.6) domain_name (0.5.20190701) @@ -121,14 +122,16 @@ GEM dotenv-rails (2.8.1) dotenv (= 2.8.1) railties (>= 3.2) - dry-inflector (1.0.0) + dry-inflector (0.2.1) + errbase (0.2.2) erubi (1.12.0) - excon (0.100.0) - faraday (2.7.10) + excon (0.109.0) + faraday (2.8.1) + base64 faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) - ffi (1.15.5) + ffi (1.16.3) fission (0.5.0) CFPropertyList (~> 2.2) fog (2.3.0) @@ -176,7 +179,7 @@ GEM fog-atmos (0.1.0) fog-core fog-xml - fog-aws (3.19.0) + fog-aws (3.21.0) fog-core (~> 2.1) fog-json (~> 1.1) fog-xml (~> 0.1) @@ -212,7 +215,7 @@ GEM fog-ecloud (0.3.0) fog-core fog-xml - fog-google (1.21.1) + fog-google (1.23.0) addressable (>= 2.7.0) fog-core (< 2.3) fog-json (~> 1.2) @@ -285,9 +288,9 @@ GEM fog-voxel (0.1.0) fog-core fog-xml - fog-vsphere (3.6.2) + fog-vsphere (3.5.2) fog-core - rbvmomi2 (~> 3.0) + rbvmomi (>= 1.9, < 3) fog-xenserver (1.0.0) fog-core fog-xml @@ -296,11 +299,11 @@ GEM fog-core nokogiri (>= 1.5.11, < 2.0.0) formatador (0.3.0) - globalid (1.1.0) - activesupport (>= 5.0) - google-apis-compute_v1 (0.74.0) + globalid (1.2.1) + activesupport (>= 6.1) + google-apis-compute_v1 (0.86.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.1) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -308,25 +311,23 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick - google-apis-dns_v1 (0.32.0) + google-apis-dns_v1 (0.36.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-iamcredentials_v1 (0.17.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-monitoring_v3 (0.47.0) + google-apis-monitoring_v3 (0.54.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-pubsub_v1 (0.40.0) + google-apis-pubsub_v1 (0.45.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-sqladmin_v1beta4 (0.53.0) + google-apis-sqladmin_v1beta4 (0.61.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.24.0) + google-apis-storage_v1 (0.32.0) google-apis-core (>= 0.11.0, < 2.a) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - googleauth (1.7.0) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) @@ -351,12 +352,13 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.6.3) - jwt (2.7.1) + json (2.7.1) + jwt (2.8.1) + base64 listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - loofah (2.21.3) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -364,90 +366,92 @@ GEM net-imap net-pop net-smtp - marcel (1.0.2) - memoist (0.16.2) + marcel (1.0.4) method_source (1.0.0) - mime-types (3.4.1) + mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2023.0218.1) + mime-types-data (3.2024.0206) mini_magick (4.12.0) - mini_mime (1.1.2) - minitest (5.18.1) + mini_mime (1.1.5) + mini_portile2 (2.8.5) + minitest (5.22.2) msgpack (1.7.2) multi_json (1.15.0) multi_xml (0.6.0) - mysql2 (0.5.5) - net-http (0.3.2) + mysql2 (0.5.6) + net-http (0.4.1) uri - net-imap (0.3.6) + net-imap (0.3.7) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.3.3) + net-smtp (0.4.0.1) net-protocol netrc (0.11.0) - nio4r (2.5.9) - nokogiri (1.15.3-arm64-darwin) + nio4r (2.7.0) + nokogiri (1.13.10) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) + nokogiri (1.13.10-arm64-darwin) racc (~> 1.4) - nokogiri (1.15.3-x86_64-linux) + nokogiri (1.13.10-x86_64-linux) racc (~> 1.4) - optimist (3.0.1) + optimist (3.1.0) orm_adapter (0.5.0) os (1.1.4) - ovirt-engine-sdk (4.4.1) + ovirt-engine-sdk (4.6.0) json (>= 1, < 3) - pagy (6.0.4) - public_suffix (5.0.3) + pagy (6.5.0) + public_suffix (5.0.4) puma (3.12.6) - racc (1.7.1) - rack (2.2.7) - rack-cache (1.14.0) + racc (1.7.3) + rack (2.2.8.1) + rack-cache (1.15.0) rack (>= 0.4) rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.4) - actioncable (= 6.1.7.4) - actionmailbox (= 6.1.7.4) - actionmailer (= 6.1.7.4) - actionpack (= 6.1.7.4) - actiontext (= 6.1.7.4) - actionview (= 6.1.7.4) - activejob (= 6.1.7.4) - activemodel (= 6.1.7.4) - activerecord (= 6.1.7.4) - activestorage (= 6.1.7.4) - activesupport (= 6.1.7.4) + rails (6.1.7.7) + actioncable (= 6.1.7.7) + actionmailbox (= 6.1.7.7) + actionmailer (= 6.1.7.7) + actionpack (= 6.1.7.7) + actiontext (= 6.1.7.7) + actionview (= 6.1.7.7) + activejob (= 6.1.7.7) + activemodel (= 6.1.7.7) + activerecord (= 6.1.7.7) + activestorage (= 6.1.7.7) + activesupport (= 6.1.7.7) bundler (>= 1.15.0) - railties (= 6.1.7.4) + railties (= 6.1.7.7) sprockets-rails (>= 2.0.0) - rails-dom-testing (2.1.1) + rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) - loofah (~> 2.21) - nokogiri (~> 1.14) - railties (6.1.7.4) - actionpack (= 6.1.7.4) - activesupport (= 6.1.7.4) + rails-html-sanitizer (1.5.0) + loofah (~> 2.19, >= 2.19.1) + railties (6.1.7.7) + actionpack (= 6.1.7.7) + activesupport (= 6.1.7.7) method_source rake (>= 12.2) thor (~> 1.0) - rake (13.0.6) + rake (13.1.0) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rbvmomi2 (3.6.1) - builder (~> 3.2) - json (~> 2.3) - nokogiri (~> 1.12, >= 1.12.5) + rbvmomi (2.4.1) + builder (~> 3.0) + json (>= 1.8) + nokogiri (~> 1.5) optimist (~> 3.0) - redis (5.0.6) - redis-client (>= 0.9.0) - redis-client (0.14.1) + redis (5.1.0) + redis-client (>= 0.17.0) + redis-client (0.21.0) connection_pool redis-namespace (1.11.0) redis (>= 4) @@ -455,7 +459,7 @@ GEM declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) - responders (3.1.0) + responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) rest-client (2.1.0) @@ -464,53 +468,56 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) retriable (3.1.2) - rexml (3.2.5) - ruby-vips (2.1.4) + rexml (3.2.6) + ruby-vips (2.2.1) ffi (~> 1.12) ruby2_keywords (0.0.5) - safely_block (0.4.0) - signet (0.17.0) + safely_block (0.3.0) + errbase (>= 0.1.1) + signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sprockets (4.2.0) + sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - thor (1.2.2) - timeout (0.4.0) + thor (1.3.1) + timeout (0.4.1) trailblazer-option (0.1.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8.2) + unf_ext (0.0.9.1) uniquify (0.1.0) - uri (0.12.2) + uri (0.13.0) warden (1.2.9) rack (>= 2.0.9) - web-console (4.2.0) + web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) webrick (1.8.1) - websocket-driver (0.7.5) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xml-simple (1.1.9) rexml - xmlrpc (0.3.2) + xmlrpc (0.3.3) webrick - zeitwerk (2.6.8) + zeitwerk (2.6.13) PLATFORMS + -darwin-22 arm64-darwin-20 + arm64-darwin-22 arm64-darwin-23 x86_64-linux @@ -543,7 +550,7 @@ DEPENDENCIES web-console (>= 3.3.0) RUBY VERSION - ruby 3.1.2p20 + ruby 2.6.3p62 BUNDLED WITH 2.4.12 diff --git a/config/.tool-versions b/config/.tool-versions new file mode 100644 index 00000000..7f44804c --- /dev/null +++ b/config/.tool-versions @@ -0,0 +1 @@ +ruby 2.6.3 diff --git a/db/migrate/20190904053235_change_desired_outcome_to_recommended_response.rb b/db/migrate/20190904053235_change_desired_outcome_to_recommended_response.rb index 7715639f..438139ae 100644 --- a/db/migrate/20190904053235_change_desired_outcome_to_recommended_response.rb +++ b/db/migrate/20190904053235_change_desired_outcome_to_recommended_response.rb @@ -1,5 +1,5 @@ class ChangeDesiredOutcomeToRecommendedResponse < ActiveRecord::Migration[6.0] def change - rename_column :reports, :desired_outcome, :recommended_response + #rename_column :reports, :desired_outcome, :recommended_response end end diff --git a/db/schema.rb b/db/schema.rb index 20d54e42..d282161d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,7 +12,7 @@ ActiveRecord::Schema.define(version: 2024_01_10_174212) do - create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false t.bigint "record_id", null: false @@ -22,7 +22,7 @@ t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true end - create_table "active_storage_blobs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "active_storage_blobs", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "key", null: false t.string "filename", null: false t.string "content_type" @@ -34,7 +34,7 @@ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true end - create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.bigint "blob_id", null: false t.string "variation_digest", null: false t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true @@ -57,7 +57,7 @@ t.string "mixer" end - create_table "ahoy_events", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "ahoy_events", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.bigint "visit_id" t.bigint "user_id" t.string "name" @@ -68,7 +68,7 @@ t.index ["visit_id"], name: "index_ahoy_events_on_visit_id" end - create_table "ahoy_visits", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "ahoy_visits", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "visit_token" t.string "visitor_token" t.bigint "user_id" @@ -96,7 +96,7 @@ t.index ["visitor_token", "started_at"], name: "index_ahoy_visits_on_visitor_token_and_started_at" end - create_table "badge_activations", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "badge_activations", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "twitch_username" t.integer "twitch_id" t.datetime "activated_on" @@ -105,7 +105,7 @@ t.datetime "updated_at", precision: 6, null: false end - create_table "comments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "comments", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.integer "commenter_id" t.integer "commentable_id" t.string "commentable_type" @@ -115,7 +115,7 @@ t.datetime "updated_at", precision: 6, null: false end - create_table "concerns", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "concerns", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "concerning_player_id" t.string "concerning_player_id_type" t.text "background" @@ -218,7 +218,7 @@ t.datetime "published_on" end - create_table "survey_invites", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "survey_invites", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "email" t.string "survey_code" t.string "surveyable_type" @@ -229,7 +229,7 @@ t.datetime "updated_at", precision: 6, null: false end - create_table "twitch_tokens", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "twitch_tokens", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "access_token" t.integer "expires_in" t.datetime "created_at", precision: 6, null: false @@ -271,7 +271,7 @@ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end - create_table "verifications", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "verifications", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "first_name" t.string "last_name" t.string "email" From b12b26dca2c0cff27f3a68ffa74d8ee0f25f1064 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Sun, 31 Mar 2024 23:18:22 -0400 Subject: [PATCH 02/26] adding spam flag --- app/models/report.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/report.rb b/app/models/report.rb index eccc9ac6..09c03036 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -6,7 +6,8 @@ class Report < ApplicationRecord warned: "Warned", revoked: "Revoked", watched: "Watched", - all: "All" + all: "All", + spam: "Spam" }.freeze IMAGE_STYLES = { From c46a2dd1438565fc8c02f7e80ba7b5d9f59685ce Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Sun, 31 Mar 2024 23:39:25 -0400 Subject: [PATCH 03/26] added spam to report model, created spam partical view --- app/models/report.rb | 1 + app/views/reports/_show_spam.html.erb | 0 app/views/reports/show.html.erb | 2 ++ db/migrate/20240401033626_add_spam_to_reports.rb | 5 +++++ db/schema.rb | 3 ++- 5 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 app/views/reports/_show_spam.html.erb create mode 100644 db/migrate/20240401033626_add_spam_to_reports.rb diff --git a/app/models/report.rb b/app/models/report.rb index 09c03036..49151d70 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -59,6 +59,7 @@ class Report < ApplicationRecord lower(incident_stream_twitch_id) LIKE :search OR lower(incident_description) LIKE :search", search: "%#{search.downcase}%") } + scope :spam, lambda { where(spam: true) } def unresolved? diff --git a/app/views/reports/_show_spam.html.erb b/app/views/reports/_show_spam.html.erb new file mode 100644 index 00000000..e69de29b diff --git a/app/views/reports/show.html.erb b/app/views/reports/show.html.erb index 44077167..ce766c6d 100644 --- a/app/views/reports/show.html.erb +++ b/app/views/reports/show.html.erb @@ -9,6 +9,8 @@ <%= render(partial: "reports/show_warned") %> <% elsif @report.revoked? %> <%= render(partial: "reports/show_revoked") %> + <% elsif @report.spam? %> + <%= render(partial: "reports/show_spam") %> <% end %> diff --git a/db/migrate/20240401033626_add_spam_to_reports.rb b/db/migrate/20240401033626_add_spam_to_reports.rb new file mode 100644 index 00000000..d7894e87 --- /dev/null +++ b/db/migrate/20240401033626_add_spam_to_reports.rb @@ -0,0 +1,5 @@ +class AddSpamToReports < ActiveRecord::Migration[6.1] + def change + add_column :reports, :spam, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index d282161d..1897c3bc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_01_10_174212) do +ActiveRecord::Schema.define(version: 2024_04_01_033626) do create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "name", null: false @@ -190,6 +190,7 @@ t.integer "reported_twitch_id" t.integer "incident_stream_twitch_id" t.bigint "ahoy_visit_id" + t.boolean "spam", default: false t.index ["ahoy_visit_id"], name: "index_reports_on_ahoy_visit_id" t.index ["reported_twitch_id"], name: "index_reports_on_reported_twitch_id" t.index ["reporter_email"], name: "index_reports_on_reporter_email" From 48dd6c65e1db0b5af55e1305310185d599a040a7 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Mon, 1 Apr 2024 00:04:56 -0400 Subject: [PATCH 04/26] added rack-attack gem: --- Gemfile | 3 +++ Gemfile.lock | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Gemfile b/Gemfile index e4e96882..8ec663b0 100644 --- a/Gemfile +++ b/Gemfile @@ -71,6 +71,9 @@ gem 'dotenv-rails' gem 'pagy' gem 'ahoy_matey' +# Paola Dev Gems +gem 'rack-attack' + # Using Dragonfly v0.9 for files & images # Because I can never get v1.0 to work with PJ's caching solution # Also, finally had to patch gem with a bug fix from the newer version diff --git a/Gemfile.lock b/Gemfile.lock index 2df3151a..430f47d5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -409,6 +409,8 @@ GEM puma (3.12.6) racc (1.7.3) rack (2.2.8.1) + rack-attack (6.7.0) + rack (>= 1.0, < 4) rack-cache (1.15.0) rack (>= 0.4) rack-test (2.1.0) @@ -541,6 +543,7 @@ DEPENDENCIES nokogiri pagy puma (~> 3.11) + rack-attack rack-cache rails (~> 6.0) redis From c90d1ed31c69086b1c0a1c389d21e31dac7796d0 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Mon, 1 Apr 2024 00:06:07 -0400 Subject: [PATCH 05/26] show spam html basic --- app/views/reports/_show_spam.html.erb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/views/reports/_show_spam.html.erb b/app/views/reports/_show_spam.html.erb index e69de29b..c39c8d54 100644 --- a/app/views/reports/_show_spam.html.erb +++ b/app/views/reports/_show_spam.html.erb @@ -0,0 +1,25 @@ +
+
Spam Badge
+
+
+ Spam <%= l(@report.spam.created_at, format: "%b. %-d, %Y · %-l:%M%P") %> [<%= link_to(@report.reviewer.display_name, user_path(@report.reviewer)) %>] +
+ <%= render(partial: "reports/watchable_toggle")%> +
+
+<%= render(partial: "reports/identifiers") %> +<%= render(partial: "reports/materials") %> +<%= render(partial: "reports/associated_pledge") %> +
+
+
Reason for Revocation
+
<%= @report.spam.reason%>
+
+
+
+
Spam
+ <%= link_to('Back', params[:back].present? ? params[:back] : reports_path, class: "back-button mini button") %> +
+<%= render(partial: "reports/reporter_details") %> +<%= render(partial: "reports/related_reports") %> +<%= render(partial: "reports/comments")%> From 1219d5d764885496221d66a04b81e8912c858814 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Mon, 1 Apr 2024 01:12:26 -0400 Subject: [PATCH 06/26] MVP of limit_request --- app/controllers/reports_controller.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 6b89f538..9916e97a 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -7,6 +7,7 @@ class ReportsController < ApplicationController before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :find_report, only: [ :show, :dismiss, :undismiss, :watch, :unwatch ] + before_action :limit_request_rate, only: [:create] around_action :display_timezone def index @@ -163,5 +164,18 @@ def display_timezone def report_params params.require(:report).permit(:reporter_email, :reporter_twitch_name, :reporter_twitch_id, :reported_twitch_name, :reported_twitch_id, :incident_stream, :incident_stream_twitch_id, :incident_occurred, :incident_description, :recommended_response, :image,) end + + def limit_request_rate + client_ip = request.remote_ip + rate_limit_key = "rate_limit:#{client_ip}" + rate_limit_count = Rails.cache.read(rate_limit_key).to_i + + if rate_limit_count >= 3 + render json: { error: 'Rate limit exceeded. Please try again later.' }, status: :too_many_requests + return + end + + Rails.cache.write(rate_limit_key, rate_limit_count + 1, expires_in: 60.seconds) + end end From 35b25b22c31776782adc58985755f80f5e15f62a Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Mon, 1 Apr 2024 01:14:08 -0400 Subject: [PATCH 07/26] using Redis --- app/controllers/reports_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 9916e97a..7af8630e 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -169,13 +169,16 @@ def limit_request_rate client_ip = request.remote_ip rate_limit_key = "rate_limit:#{client_ip}" rate_limit_count = Rails.cache.read(rate_limit_key).to_i + + # count = Redis.current.get(key).to_i if rate_limit_count >= 3 render json: { error: 'Rate limit exceeded. Please try again later.' }, status: :too_many_requests return end - + Rails.cache.write(rate_limit_key, rate_limit_count + 1, expires_in: 60.seconds) + # Redis.current.set(key, count + 1, ex: interval) end end From 45d4fae580a708d09265a9a9ba4aa9e7e6ebc952 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Tue, 9 Apr 2024 23:17:48 -0400 Subject: [PATCH 08/26] fixed MVP of rate lmit --- app/controllers/reports_controller.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 7af8630e..308973e2 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -7,7 +7,7 @@ class ReportsController < ApplicationController before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :find_report, only: [ :show, :dismiss, :undismiss, :watch, :unwatch ] - before_action :limit_request_rate, only: [:create] + before_action :limit_request_rate, only: [ :create] around_action :display_timezone def index @@ -169,16 +169,16 @@ def limit_request_rate client_ip = request.remote_ip rate_limit_key = "rate_limit:#{client_ip}" rate_limit_count = Rails.cache.read(rate_limit_key).to_i - - # count = Redis.current.get(key).to_i - if rate_limit_count >= 3 - render json: { error: 'Rate limit exceeded. Please try again later.' }, status: :too_many_requests + if rate_limit_count >= 9 + flash[:alert] = "Rate limit exceeded. Please try again later." + puts " Exxceeded #{rate_limit_count}" + redirect_to new_report_path + return end Rails.cache.write(rate_limit_key, rate_limit_count + 1, expires_in: 60.seconds) - # Redis.current.set(key, count + 1, ex: interval) end end From 4ba80a9e88e0ee2a5cf93df76024c4ac6ba70e29 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Tue, 9 Apr 2024 23:19:58 -0400 Subject: [PATCH 09/26] smalle clean up --- app/controllers/reports_controller.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 308973e2..88b6ae9a 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -172,10 +172,7 @@ def limit_request_rate if rate_limit_count >= 9 flash[:alert] = "Rate limit exceeded. Please try again later." - puts " Exxceeded #{rate_limit_count}" redirect_to new_report_path - - return end Rails.cache.write(rate_limit_key, rate_limit_count + 1, expires_in: 60.seconds) From b106b000f6a566a43e866baeca4850fcd1ce5c45 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Wed, 10 Apr 2024 10:37:04 -0400 Subject: [PATCH 10/26] dry code to extend MVP solution to other controllers --- app/controllers/concerns/rate_limitable.rb | 19 +++++++++++++++++++ app/controllers/reports_controller.rb | 18 +++++------------- 2 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 app/controllers/concerns/rate_limitable.rb diff --git a/app/controllers/concerns/rate_limitable.rb b/app/controllers/concerns/rate_limitable.rb new file mode 100644 index 00000000..b4b453b8 --- /dev/null +++ b/app/controllers/concerns/rate_limitable.rb @@ -0,0 +1,19 @@ +module RateLimitable + + private + + def limit_create_request(base_key, redirection_path, limit = 9) + client_ip = request.remote_ip + + rate_limit_key = "#{base_key}_rate_limit:#{client_ip}" + rate_limit_count = Rails.cache.read(rate_limit_key).to_i + + if rate_limit_count >= limit + flash[:alert] = "Rate limit exceeded. Please try again later." + redirect_to redirection_path + end + + Rails.cache.write(rate_limit_key, rate_limit_count + 1, expires_in: 60.seconds) + end + +end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 88b6ae9a..a6d25a11 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -1,5 +1,5 @@ class ReportsController < ApplicationController - + include RateLimitable layout "backstage", only: [ :index, :show ] skip_before_action :verify_authenticity_token, only: [ :watch, :unwatch, :twitch_lookup ] @@ -7,7 +7,7 @@ class ReportsController < ApplicationController before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :find_report, only: [ :show, :dismiss, :undismiss, :watch, :unwatch ] - before_action :limit_request_rate, only: [ :create] + before_action :apply_request_rate, only: [ :create] around_action :display_timezone def index @@ -165,17 +165,9 @@ def report_params params.require(:report).permit(:reporter_email, :reporter_twitch_name, :reporter_twitch_id, :reported_twitch_name, :reported_twitch_id, :incident_stream, :incident_stream_twitch_id, :incident_occurred, :incident_description, :recommended_response, :image,) end - def limit_request_rate - client_ip = request.remote_ip - rate_limit_key = "rate_limit:#{client_ip}" - rate_limit_count = Rails.cache.read(rate_limit_key).to_i - - if rate_limit_count >= 9 - flash[:alert] = "Rate limit exceeded. Please try again later." - redirect_to new_report_path - end - - Rails.cache.write(rate_limit_key, rate_limit_count + 1, expires_in: 60.seconds) + def apply_request_rate + puts "applying rate limit" + limit_create_request("reports", new_report_path, 1) end end From 377a8a2e7ab535b5aff1ce74770ab45cc3324ae8 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Wed, 10 Apr 2024 10:38:09 -0400 Subject: [PATCH 11/26] change report rate limit from 2 to 10 --- app/controllers/reports_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index a6d25a11..f184c545 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -167,7 +167,7 @@ def report_params def apply_request_rate puts "applying rate limit" - limit_create_request("reports", new_report_path, 1) + limit_create_request("reports", new_report_path, 9) end end From 37773b4ebe5dc0845d16425f6ee6e1ad05d368bd Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Wed, 10 Apr 2024 10:47:35 -0400 Subject: [PATCH 12/26] extended soultion --- app/controllers/concerns_controller.rb | 9 +++++++-- app/controllers/pledges_controller.rb | 7 ++++++- app/controllers/reports_controller.rb | 3 +-- app/controllers/verifications_controller.rb | 8 +++++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/controllers/concerns_controller.rb b/app/controllers/concerns_controller.rb index cec21137..fb33540c 100644 --- a/app/controllers/concerns_controller.rb +++ b/app/controllers/concerns_controller.rb @@ -1,5 +1,5 @@ class ConcernsController < ApplicationController - + include RateLimitable layout "backstage", only: [ :index, :show ] skip_before_action :verify_authenticity_token, only: [ :watch, :unwatch ] @@ -8,7 +8,8 @@ class ConcernsController < ApplicationController before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss, :review, :watch, :unwatch ] before_action :find_concern, only: [ :show, :dismiss, :undismiss, :review, :watch, :unwatch ] around_action :display_timezone - + before_action :apply_request_rate, only: [ :create] + def index # f is used to filter reports by scope # q is used to search for keywords @@ -132,5 +133,9 @@ def display_timezone def concern_params params.require(:concern).permit(:concerning_player_id, :concerning_player_id_type, :background, :description, :recommended_response, :concerned_email, :concerned_cert_code, screenshots: []) end + + def apply_request_rate + limit_create_request("concerns", new_concern_path) + end end diff --git a/app/controllers/pledges_controller.rb b/app/controllers/pledges_controller.rb index fb7577a1..dc887a02 100644 --- a/app/controllers/pledges_controller.rb +++ b/app/controllers/pledges_controller.rb @@ -1,11 +1,12 @@ class PledgesController < ApplicationController - + include RateLimitable layout "backstage", only: [ :index ] before_action :authenticate_user!, only: [ :index ] before_action :ensure_staff, only: [ :index ] before_action :find_pledge, only: [ :show ] before_action :handle_twitch_auth, only: [ :new ] + before_action :apply_request_rate, only: [ :create] def index # f is used to filter reports by scope @@ -218,5 +219,9 @@ def ensure_staff def pledge_params params.require(:pledge).permit(:first_name, :last_name, :email) end + + def apply_request_rate + limit_create_request("pledges", new_pledge_path) + end end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index f184c545..64a2c83b 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -166,8 +166,7 @@ def report_params end def apply_request_rate - puts "applying rate limit" - limit_create_request("reports", new_report_path, 9) + limit_create_request("reports", new_report_path) end end diff --git a/app/controllers/verifications_controller.rb b/app/controllers/verifications_controller.rb index 8167017b..4ac249bb 100644 --- a/app/controllers/verifications_controller.rb +++ b/app/controllers/verifications_controller.rb @@ -1,5 +1,5 @@ class VerificationsController < ApplicationController - + include RateLimitable layout "backstage", only: [ :index, :show, :verify_eligibility, :deny_eligibility, :withdraw_eligibility ] skip_before_action :verify_authenticity_token, only: [ :watch, :unwatch ] @@ -10,7 +10,9 @@ class VerificationsController < ApplicationController :verify, :deny, :ignore, :withdraw, :voucher, :resend_cert, :watch, :unwatch ] before_action :find_verification, only: [ :show, :verify_eligibility, :deny_eligibility, :withdraw_eligibility, :verify, :deny, :ignore, :withdraw, :voucher, :resend_cert, :watch, :unwatch ] + before_action :apply_request_rate, only: [ :create] around_action :display_timezone + def index # f is used to filter reports by scope @@ -214,5 +216,9 @@ def display_timezone def verification_params params.require(:verification).permit(:first_name, :last_name, :email, :birth_date, :discord_username, :player_id_type, :player_id, :player_id_and_discord, :gender, :pronouns, :photo_id, :doctors_note, :social_profile, :voice_requested, :additional_notes) end + + def apply_request_rate + limit_create_request("verification", new_verification_path) + end end From 3cd71fafdecfddfc852f13bbdf4a8e787d253842 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Thu, 25 Apr 2024 14:46:38 -0400 Subject: [PATCH 13/26] tested implemntation on reports, verifications, and pledges --- Gemfile | 2 +- app/controllers/concerns/rate_limitable.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 8ec663b0..dbac1c06 100644 --- a/Gemfile +++ b/Gemfile @@ -72,7 +72,7 @@ gem 'pagy' gem 'ahoy_matey' # Paola Dev Gems -gem 'rack-attack' +# gem 'rack-attack' # Using Dragonfly v0.9 for files & images # Because I can never get v1.0 to work with PJ's caching solution diff --git a/app/controllers/concerns/rate_limitable.rb b/app/controllers/concerns/rate_limitable.rb index b4b453b8..a7756d84 100644 --- a/app/controllers/concerns/rate_limitable.rb +++ b/app/controllers/concerns/rate_limitable.rb @@ -1,5 +1,4 @@ module RateLimitable - private def limit_create_request(base_key, redirection_path, limit = 9) From 1cf47e771632c6609b6d9303721319d5059a78b6 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Thu, 25 Apr 2024 16:48:58 -0400 Subject: [PATCH 14/26] MVP of filtering reports based on same data --- app/controllers/reports_controller.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 64a2c83b..5bc49fb9 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -60,6 +60,8 @@ def create if @report.incident_stream && @report.incident_stream_twitch_id.blank? @report.incident_stream_twitch_id = lookup_twitch_id(@report.incident_stream) end + + find_report_matches() if @report.save # Email notification to staff @@ -149,6 +151,22 @@ def find_report redirect_to staff_index_path end + def find_report_matches + # Fetch reports with matching required attributes + report_matches = Report.where( + reporter_email: @report.reporter_email, + reported_twitch_id: @report.reported_twitch_id, + incident_stream_twitch_id: @report.incident_stream_twitch_id, + incident_description: @report.incident_description, + incident_occurred: @report.incident_occurred + ) + + # Check if the number of matches is greater than 5 + if report_matches.count > 5 + report_matches.update_all(spam: true) + end + end + private def ensure_staff unless current_user.is_moderator? || current_user.is_admin? From 92f75592161ab1d2b6ac6a852417360bb3ee3752 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Thu, 25 Apr 2024 16:55:41 -0400 Subject: [PATCH 15/26] threshold of identical data --- app/controllers/reports_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 5bc49fb9..149dea3b 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -162,6 +162,7 @@ def find_report_matches ) # Check if the number of matches is greater than 5 + # Need to determine what a better threshold if report_matches.count > 5 report_matches.update_all(spam: true) end From d8af5643d1a258ce8c714573911c36d3221c80a0 Mon Sep 17 00:00:00 2001 From: Paola Calle <98432607+paolacalle@users.noreply.github.com> Date: Mon, 6 May 2024 17:28:08 -0400 Subject: [PATCH 16/26] twitch additional instructions added an additional step for twitch setup --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ada18b90..4f931b38 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,19 @@ The AnyKey app uses several external services: * Stripe for donation payments * Twitch for GLHF pledge badge assignment and moderation -In order to test all of the features in your development environment you will have to add additional credentials to your `.env` file. These credentials are only available to trusted collaborators and can be obtained from the repository manager. -Note that the `TWITCH_REDIRECT_URL` must be set in both the external Twitch app and the local development environment. A separate Twitch app should be created by the developer for local testing purposes. +### Notes for Twitch API + +* Make sure to run + ```shell + rake twitch_token:request + ``` +* Note that the `TWITCH_REDIRECT_URL` must be set in both the external Twitch app and the local development environment. +* A separate Twitch app should be created by the developer for local testing purposes. + +### Environment Credentials + +In order to test all of the features in your development environment you will have to add additional credentials to your `.env` file. These credentials are only available to trusted collaborators and can be obtained from the repository manager. ```shell SENDGRID_USERNAME=XXX From 2e124220ad169c7704f1d65c5ea548de87249d5e Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Wed, 8 May 2024 11:02:07 -0400 Subject: [PATCH 17/26] re-organized and made expriy an optional param --- app/controllers/concerns/rate_limitable.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/controllers/concerns/rate_limitable.rb b/app/controllers/concerns/rate_limitable.rb index a7756d84..38449c44 100644 --- a/app/controllers/concerns/rate_limitable.rb +++ b/app/controllers/concerns/rate_limitable.rb @@ -1,18 +1,19 @@ module RateLimitable private - def limit_create_request(base_key, redirection_path, limit = 9) - client_ip = request.remote_ip + CACHE_KEY_PATTERN = '%{base}_rate_limit:%{ip}'.freeze - rate_limit_key = "#{base_key}_rate_limit:#{client_ip}" + def limit_create_request(base_key, redirection_path, limit = 9, expiry: 60) + client_ip = request.remote_ip + rate_limit_key = CACHE_KEY_PATTERN % { base: base_key, ip: client_ip } rate_limit_count = Rails.cache.read(rate_limit_key).to_i if rate_limit_count >= limit - flash[:alert] = "Rate limit exceeded. Please try again later." + flash[:alert] = 'Rate limit exceeded. Please try again later.' redirect_to redirection_path end - Rails.cache.write(rate_limit_key, rate_limit_count + 1, expires_in: 60.seconds) + Rails.cache.write(rate_limit_key, rate_limit_count + 1, expires_in: expiry.seconds) end end From 2600e31ef734e8b9a39cd0781e25dbebfe556d94 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Wed, 8 May 2024 12:16:41 -0400 Subject: [PATCH 18/26] device signature rate limit --- app/controllers/concerns/rate_limitable.rb | 40 ++++++++++++++++------ app/controllers/reports_controller.rb | 21 +++++++----- app/models/report.rb | 5 ++- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/app/controllers/concerns/rate_limitable.rb b/app/controllers/concerns/rate_limitable.rb index 38449c44..18933a8b 100644 --- a/app/controllers/concerns/rate_limitable.rb +++ b/app/controllers/concerns/rate_limitable.rb @@ -1,19 +1,37 @@ module RateLimitable private - CACHE_KEY_PATTERN = '%{base}_rate_limit:%{ip}'.freeze + CACHE_KEY_PATTERN = '%{type}:%{base_key}:%{identifier}'.freeze - def limit_create_request(base_key, redirection_path, limit = 9, expiry: 60) - client_ip = request.remote_ip - rate_limit_key = CACHE_KEY_PATTERN % { base: base_key, ip: client_ip } - rate_limit_count = Rails.cache.read(rate_limit_key).to_i - - if rate_limit_count >= limit - flash[:alert] = 'Rate limit exceeded. Please try again later.' - redirect_to redirection_path - end + def limit_request_by_ip(base_key, redirection_path, limit = 9, expiry = 60) + client_ip = request.remote_ip + rate_limit_key = CACHE_KEY_PATTERN % {type: "ip_rate_limit", base_key: base_key, identifier: client_ip} + rate_limit_count = Rails.cache.read(rate_limit_count).to_i + end + + def limit_request_by_signature(base_key, redirection_path, limit = 9, expiry = 60) + device_signature = generate_device_signature(request) + rate_limit_key = CACHE_KEY_PATTERN % { type: 'device_rate_limit', base_key: base_key, identifier: device_signature } + rate_limit_count = Rails.cache.read(rate_limit_key).to_i - Rails.cache.write(rate_limit_key, rate_limit_count + 1, expires_in: expiry.seconds) + process_rate_limit(rate_limit_key, rate_limit_count, limit, expiry, redirection_path) + end + + def generate_device_signature(request) + user_agent = request.user_agent || "" + accept_language = request.env['HTTP_ACCEPT_LANGUAGE'] || "" + Digest::SHA1.hexdigest([request.remote_ip, user_agent, accept_language].join(':')) # unique device signature end + def process_rate_limit(key, current_count, limit, expiry, redirect_path) + if current_count >= limit + ahoy.track "Rate limit exceeded", key: key + flash[:alert] = 'Rate limit exceeded. Please try again later.' + redirect_to redirect_path + return false + else + Rails.cache.write(key, current_count + 1, expires_in: expiry.seconds) + return true + end + end end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 149dea3b..a3bca5c6 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -47,7 +47,13 @@ def new def create @report = Report.new(report_params) - + + # First check IP-based rate limiting + return unless limit_request_by_ip('report_create', new_report_path) + + # Fallback to device signature-based limiting (may not be needed) + return unless limit_request_by_signature('report_create', new_report_path) + # Lookup Twitch IDs (if not fetched via Ajax) if @report.reporter_twitch_name && @report.reporter_twitch_id.blank? @report.reporter_twitch_id = lookup_twitch_id(@report.reporter_twitch_name) @@ -61,7 +67,7 @@ def create @report.incident_stream_twitch_id = lookup_twitch_id(@report.incident_stream) end - find_report_matches() + check_report_matches() if @report.save # Email notification to staff @@ -151,7 +157,7 @@ def find_report redirect_to staff_index_path end - def find_report_matches + def check_report_matches # Fetch reports with matching required attributes report_matches = Report.where( reporter_email: @report.reporter_email, @@ -168,6 +174,10 @@ def find_report_matches end end + def check_report_device + + end + private def ensure_staff unless current_user.is_moderator? || current_user.is_admin? @@ -183,9 +193,4 @@ def display_timezone def report_params params.require(:report).permit(:reporter_email, :reporter_twitch_name, :reporter_twitch_id, :reported_twitch_name, :reported_twitch_id, :incident_stream, :incident_stream_twitch_id, :incident_occurred, :incident_description, :recommended_response, :image,) end - - def apply_request_rate - limit_create_request("reports", new_report_path) - end - end diff --git a/app/models/report.rb b/app/models/report.rb index 49151d70..d913a1e5 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -106,7 +106,7 @@ def related_reports protected def ensure_sane_review - if (self.dismissed || self.warned || self.revoked) && !(self.dismissed ^ self.warned ^ self.revoked) + if (self.dismissed || self.warned || self.revoked || self.spam) && !(self.dismissed ^ self.warned ^ self.revoked ^ self.spam) if self.dismissed errors.add(:dismissed, "must be the only status flag set") end @@ -116,6 +116,9 @@ def ensure_sane_review if self.revoked errors.add(:revoked, "must be the only status flag set") end + if self.spam + errors.add(:spam, "must be the only status flag set") + end end end From ad8ecd976a4338436930b8c8aed1c702a12f2640 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Wed, 8 May 2024 13:33:56 -0400 Subject: [PATCH 19/26] text simillarity --- Gemfile | 1 + Gemfile.lock | 7 ++-- app/controllers/reports_controller.rb | 52 +++++++++++++++++++-------- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index dbac1c06..d6e76adb 100644 --- a/Gemfile +++ b/Gemfile @@ -73,6 +73,7 @@ gem 'ahoy_matey' # Paola Dev Gems # gem 'rack-attack' +gem 'tf-idf-similarity' # Using Dragonfly v0.9 for files & images # Because I can never get v1.0 to work with PJ's caching solution diff --git a/Gemfile.lock b/Gemfile.lock index 430f47d5..924c7a77 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -409,8 +409,6 @@ GEM puma (3.12.6) racc (1.7.3) rack (2.2.8.1) - rack-attack (6.7.0) - rack (>= 1.0, < 4) rack-cache (1.15.0) rack (>= 0.4) rack-test (2.1.0) @@ -488,6 +486,8 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) + tf-idf-similarity (0.3.0) + unicode_utils (~> 1.4) thor (1.3.1) timeout (0.4.1) trailblazer-option (0.1.2) @@ -497,6 +497,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.9.1) + unicode_utils (1.4.0) uniquify (0.1.0) uri (0.13.0) warden (1.2.9) @@ -543,11 +544,11 @@ DEPENDENCIES nokogiri pagy puma (~> 3.11) - rack-attack rack-cache rails (~> 6.0) redis redis-namespace + tf-idf-similarity tzinfo-data uniquify web-console (>= 3.3.0) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index a3bca5c6..077f4628 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -1,5 +1,8 @@ class ReportsController < ApplicationController include RateLimitable + require 'tf-idf-similarity' + require 'matrix' + layout "backstage", only: [ :index, :show ] skip_before_action :verify_authenticity_token, only: [ :watch, :unwatch, :twitch_lookup ] @@ -7,7 +10,6 @@ class ReportsController < ApplicationController before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :find_report, only: [ :show, :dismiss, :undismiss, :watch, :unwatch ] - before_action :apply_request_rate, only: [ :create] around_action :display_timezone def index @@ -158,25 +160,47 @@ def find_report end def check_report_matches - # Fetch reports with matching required attributes - report_matches = Report.where( - reporter_email: @report.reporter_email, - reported_twitch_id: @report.reported_twitch_id, - incident_stream_twitch_id: @report.incident_stream_twitch_id, - incident_description: @report.incident_description, - incident_occurred: @report.incident_occurred + time_threshold = 2.hour + current_time = Time.current + time_window_start = current_time - time_threshold + time_window_end = current_time + time_threshold + + potential_matches = Report.where( + reporter_email: @report.reporter_email, + reported_twitch_id: @report.reported_twitch_id, + created_at: time_window_start..time_window_end # time range ) - # Check if the number of matches is greater than 5 - # Need to determine what a better threshold - if report_matches.count > 5 - report_matches.update_all(spam: true) + # mark as spam based on a similarity threshold + spam_found = false + potential_matches.find_each do |match| + if similarity_score(@report, match) >= 0.9 + match.update(spam: true, dismissed: false, warned: false, revoked: false, watched: false) + end + end + + if spam_found + @report.update(spam: true) end end - def check_report_device + def similarity_score(report1, report2) + # weighted average of description and time similarities + description_similarity = text_similarity(report1.incident_description, report2.incident_description) + end - end + def text_similarity(text1, text2) + + documents = [ + TfIdfSimilarity::Document.new(text1), + TfIdfSimilarity::Document.new(text2) + ] + + model = TfIdfSimilarity::TfIdfModel.new(documents) + matrix = model.similarity_matrix + + return matrix[0, 1] + end private def ensure_staff From 008d1a619f81efee1eeb55e90708f54d8f0547bc Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Wed, 8 May 2024 13:36:48 -0400 Subject: [PATCH 20/26] update flags for orginal report --- app/controllers/reports_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 077f4628..a0364a41 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -180,7 +180,7 @@ def check_report_matches end if spam_found - @report.update(spam: true) + @report.update(spam: true, dismissed: false, warned: false, revoked: false, watched: false) end end From 5a969aac76802456c0a9bb9e37a309ad51744de1 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Fri, 10 May 2024 10:56:20 -0400 Subject: [PATCH 21/26] test spam + spam report model --- app/controllers/reports_controller.rb | 40 ++++++++-- app/models/report.rb | 14 +++- app/models/spam_report.rb | 17 +++++ app/views/reports/_related_spam.html.erb | 25 +++++++ app/views/reports/_show_spam.html.erb | 15 +--- .../20240510134850_create_spam_reports.rb | 12 +++ db/schema.rb | 14 +++- db/test_data.rb | 74 +++++++++++++++++++ lib/tasks/mock_spam.rake | 7 ++ test/fixtures/spam_reports.yml | 11 +++ test/models/spam_report_test.rb | 7 ++ 11 files changed, 216 insertions(+), 20 deletions(-) create mode 100644 app/models/spam_report.rb create mode 100644 app/views/reports/_related_spam.html.erb create mode 100644 db/migrate/20240510134850_create_spam_reports.rb create mode 100644 db/test_data.rb create mode 100644 lib/tasks/mock_spam.rake create mode 100644 test/fixtures/spam_reports.yml create mode 100644 test/models/spam_report_test.rb diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index a0364a41..e6c3678a 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -10,8 +10,9 @@ class ReportsController < ApplicationController before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] before_action :find_report, only: [ :show, :dismiss, :undismiss, :watch, :unwatch ] + after_action :check_report_matches, only: [ :create] around_action :display_timezone - + def index # f is used to filter reports by scope # q is used to search for keywords @@ -41,6 +42,7 @@ def show @pledge = @report.reported_pledge @reporter_pledge = @report.reporter_pledge @related_reports = @report.related_reports + @spam_reports = @report.related_spam_reports end def new @@ -56,6 +58,7 @@ def create # Fallback to device signature-based limiting (may not be needed) return unless limit_request_by_signature('report_create', new_report_path) + puts "here" # Lookup Twitch IDs (if not fetched via Ajax) if @report.reporter_twitch_name && @report.reporter_twitch_id.blank? @report.reporter_twitch_id = lookup_twitch_id(@report.reporter_twitch_name) @@ -68,8 +71,6 @@ def create if @report.incident_stream && @report.incident_stream_twitch_id.blank? @report.incident_stream_twitch_id = lookup_twitch_id(@report.incident_stream) end - - check_report_matches() if @report.save # Email notification to staff @@ -174,8 +175,9 @@ def check_report_matches # mark as spam based on a similarity threshold spam_found = false potential_matches.find_each do |match| - if similarity_score(@report, match) >= 0.9 + if similarity_score(@report, match) >= 0.70 match.update(spam: true, dismissed: false, warned: false, revoked: false, watched: false) + spam_found = true end end @@ -185,8 +187,36 @@ def check_report_matches end def similarity_score(report1, report2) - # weighted average of description and time similarities description_similarity = text_similarity(report1.incident_description, report2.incident_description) + + # recommended_response_similarity based on whether the responses exist + + if report1.recommended_response.empty? && report2.recommended_response.empty? + recommended_response_similarity = 1.0 # nil values as a perfect match + + elsif !report1.recommended_response.empty? && !report2.recommended_response.empty? + recommended_response_similarity = text_similarity(report1.recommended_response, report2.recommended_response) + else + recommended_response_similarity = 0 + end + + # weighted avg of both where required weighs more + spam_info_value = "Description Simillarity of #{description_similarity} and Recommend Simillarity of #{recommended_response_similarity}" + + spam_report = SpamReport.new( + description: spam_info_value, + report1: report1, + report2: report2 + ) + + if spam_report.save + puts "SpamReport created successfully." + else + puts "Failed to create SpamReport: #{spam_report.errors.full_messages.join(", ")}" + end + + return (0.5 * description_similarity + 0.5 * recommended_response_similarity) + end def text_similarity(text1, text2) diff --git a/app/models/report.rb b/app/models/report.rb index d913a1e5..1593314e 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -39,6 +39,10 @@ class Report < ApplicationRecord has_one :revocation has_many :comments, as: :commentable + + # report can be referenced by multiple SpamReports in two different fields + has_many :spam_reports_as_report1, class_name: "SpamReport", foreign_key: "report1_id" + has_many :spam_reports_as_report2, class_name: "SpamReport", foreign_key: "report2_id" image_accessor :image @@ -63,9 +67,9 @@ class Report < ApplicationRecord def unresolved? - self.dismissed == false && self.warned == false && self.revoked == false + self.dismissed == false && self.warned == false && self.revoked == false && self.spam == false end - + def word_count return (self.incident_description + " " + self.recommended_response).gsub(/[^\w\s]/,"").split.count end @@ -103,6 +107,12 @@ def related_reports Report.where.not(id: self.id).where(reported_twitch_id: self.reported_twitch_id) end end + + def related_spam_reports + unless self.reported_twitch_id.blank? + SpamReport.where("report1_id = ? or report2_id = ?", self.id, self.id).distinct #unique + end + end protected def ensure_sane_review diff --git a/app/models/spam_report.rb b/app/models/spam_report.rb new file mode 100644 index 00000000..9f05140d --- /dev/null +++ b/app/models/spam_report.rb @@ -0,0 +1,17 @@ +class SpamReport < ApplicationRecord + # foreign keys for the two reports + belongs_to :report1, class_name: "Report", foreign_key: "report1_id" + belongs_to :report2, class_name: "Report", foreign_key: "report2_id" + + # validations + validates :description, presence: true + validates :report1_id, presence: true + validates :report2_id, presence: true + validate :reports_must_be_different + + private + def reports_must_be_different + errors.add(:report2_id, "must be different from report1") if report1_id == report2_id + end + +end \ No newline at end of file diff --git a/app/views/reports/_related_spam.html.erb b/app/views/reports/_related_spam.html.erb new file mode 100644 index 00000000..9a30e31c --- /dev/null +++ b/app/views/reports/_related_spam.html.erb @@ -0,0 +1,25 @@ +
+
+
+
+ Related Report Spams <%= @spam_reports.present? ? "(#{@spam_reports.size})" : ''%> +
+ <% if @spam_reports.present? %> +
+
    + <% @spam_reports.each do |spam_report| %> +
  • + Description: <%= spam_report.description %>
    + Related as Report1 ID: <%= spam_report.report1_id %>
    + Related as Report2 ID: <%= spam_report.report2_id %> +
  • + <% end %> +
+
+ <% else %> +
+ No related badge abuse reports found. +
+ <% end %> +
+
\ No newline at end of file diff --git a/app/views/reports/_show_spam.html.erb b/app/views/reports/_show_spam.html.erb index c39c8d54..279d73f6 100644 --- a/app/views/reports/_show_spam.html.erb +++ b/app/views/reports/_show_spam.html.erb @@ -2,7 +2,7 @@
Spam Badge
- Spam <%= l(@report.spam.created_at, format: "%b. %-d, %Y · %-l:%M%P") %> [<%= link_to(@report.reviewer.display_name, user_path(@report.reviewer)) %>] + Spam <%= l(@report.created_at, format: "%b. %-d, %Y · %-l:%M%P") %>
<%= render(partial: "reports/watchable_toggle")%>
@@ -10,16 +10,7 @@ <%= render(partial: "reports/identifiers") %> <%= render(partial: "reports/materials") %> <%= render(partial: "reports/associated_pledge") %> -
-
-
Reason for Revocation
-
<%= @report.spam.reason%>
-
-
-
-
Spam
- <%= link_to('Back', params[:back].present? ? params[:back] : reports_path, class: "back-button mini button") %> -
+ <%= render(partial: "reports/reporter_details") %> -<%= render(partial: "reports/related_reports") %> +<%= render(partial: "reports/related_spam") %> <%= render(partial: "reports/comments")%> diff --git a/db/migrate/20240510134850_create_spam_reports.rb b/db/migrate/20240510134850_create_spam_reports.rb new file mode 100644 index 00000000..d0b59e83 --- /dev/null +++ b/db/migrate/20240510134850_create_spam_reports.rb @@ -0,0 +1,12 @@ +class CreateSpamReports < ActiveRecord::Migration[6.1] + def change + create_table :spam_reports do |t| + t.text :description + + t.references :report1, null: false, foreign_key: { to_table: :reports } + t.references :report2, null: false, foreign_key: { to_table: :reports } + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 1897c3bc..d0ecb8a2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_04_01_033626) do +ActiveRecord::Schema.define(version: 2024_05_10_134850) do create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "name", null: false @@ -208,6 +208,16 @@ t.index ["reviewer_id"], name: "index_revocations_on_reviewer_id" end + create_table "spam_reports", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| + t.text "description" + t.bigint "report1_id", null: false + t.bigint "report2_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["report1_id"], name: "index_spam_reports_on_report1_id" + t.index ["report2_id"], name: "index_spam_reports_on_report2_id" + end + create_table "stories", charset: "utf8mb4", collation: "utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "headline" t.text "description" @@ -309,4 +319,6 @@ add_foreign_key "conduct_warnings", "users", column: "reviewer_id" add_foreign_key "reports", "users", column: "reviewer_id" add_foreign_key "revocations", "users", column: "reviewer_id" + add_foreign_key "spam_reports", "reports", column: "report1_id" + add_foreign_key "spam_reports", "reports", column: "report2_id" end diff --git a/db/test_data.rb b/db/test_data.rb new file mode 100644 index 00000000..4d7b30a5 --- /dev/null +++ b/db/test_data.rb @@ -0,0 +1,74 @@ +# Create Reports for testing the check_report_matches functionality + +# Constants +reporter_email = "test@example.com" +reported_twitch_id = "123456789" +reported_twitch_name = "pcalle2" +incident_occurred = "pcalle2" +incident_stream = "pcalle2" +incident_stream_twitch_id = "123" + +# Report 1: Exact Match within Time Window +Report.create( + reporter_email: reporter_email, + reported_twitch_id: reported_twitch_id, + incident_description: "Report about an issue", + recommended_response: "Immediate action required", + reported_twitch_name: reported_twitch_name, + incident_occurred: incident_occurred, + incident_stream_twitch_id: incident_stream_twitch_id, + incident_stream: incident_stream, + created_at: 1.hour.ago +) + +# Report 2: Match with slightly different descriptions and responses +Report.create( + reporter_email: reporter_email, + reported_twitch_id: reported_twitch_id, + incident_description: "Detailed report about an issue", + recommended_response: "Immediate action needed", + reported_twitch_name: reported_twitch_name, + incident_occurred: incident_occurred, + incident_stream_twitch_id: incident_stream_twitch_id, + incident_stream: incident_stream, + created_at: 1.hour.ago +) + +# Report 3: Outside Time Window +Report.create( + reporter_email: reporter_email, + reported_twitch_id: reported_twitch_id, + incident_description: "Old report about an unrelated issue", + recommended_response: "Review later", + reported_twitch_name: reported_twitch_name, + incident_occurred: incident_occurred, + incident_stream_twitch_id: incident_stream_twitch_id, + incident_stream: incident_stream, + created_at: 3.days.ago +) + +# Report 4: No similarity in description or response +Report.create( + reporter_email: reporter_email, + reported_twitch_id: reported_twitch_id, + incident_description: "Unrelated issue report", + recommended_response: '', # No recommended response + reported_twitch_name: reported_twitch_name, + incident_occurred: incident_occurred, + incident_stream_twitch_id: incident_stream_twitch_id, + incident_stream: incident_stream, + created_at: 30.minutes.ago +) + +# Report 5: Report with nil recommended_response matching another nil +Report.create( + reporter_email: reporter_email, + reported_twitch_id: reported_twitch_id, + incident_description: "Issue report with no response recommended", + recommended_response: "", + reported_twitch_name: reported_twitch_name, + incident_occurred: incident_occurred, + incident_stream_twitch_id: incident_stream_twitch_id, + incident_stream: incident_stream, + created_at: 2.hours.ago +) diff --git a/lib/tasks/mock_spam.rake b/lib/tasks/mock_spam.rake new file mode 100644 index 00000000..e4edb815 --- /dev/null +++ b/lib/tasks/mock_spam.rake @@ -0,0 +1,7 @@ +namespace :db do + desc "Load custom seed data" + task :load_custom_data => :environment do + load Rails.root.join('db', 'test_data.rb') + end + end + \ No newline at end of file diff --git a/test/fixtures/spam_reports.yml b/test/fixtures/spam_reports.yml new file mode 100644 index 00000000..49d5c0ac --- /dev/null +++ b/test/fixtures/spam_reports.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + description: MyText + report1: one + report2: one + +two: + description: MyText + report1: two + report2: two diff --git a/test/models/spam_report_test.rb b/test/models/spam_report_test.rb new file mode 100644 index 00000000..1566db2f --- /dev/null +++ b/test/models/spam_report_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class SpamReportTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From d461bede5bced8aa0945ae97ef360e08b4b1b2c3 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Fri, 10 May 2024 11:09:01 -0400 Subject: [PATCH 22/26] remove the spam reports from unresolved --- app/controllers/reports_controller.rb | 25 +++++++++++++----------- app/models/report.rb | 2 +- app/views/reports/_related_spam.html.erb | 3 +-- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index e6c3678a..ee294e01 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -175,9 +175,17 @@ def check_report_matches # mark as spam based on a similarity threshold spam_found = false potential_matches.find_each do |match| - if similarity_score(@report, match) >= 0.70 - match.update(spam: true, dismissed: false, warned: false, revoked: false, watched: false) - spam_found = true + score, spam_report = similarity_score(@report, match) + + if score >= 0.80 + if spam_report.save + match.update(spam: true, dismissed: false, warned: false, revoked: false, watched: false) + spam_found = true + puts "SpamReport created successfully." + else + puts "Failed to create SpamReport: #{spam_report.errors.full_messages.join(", ")}" + end + end end @@ -191,10 +199,10 @@ def similarity_score(report1, report2) # recommended_response_similarity based on whether the responses exist - if report1.recommended_response.empty? && report2.recommended_response.empty? + if report1.recommended_response.blank? && report2.recommended_response.blank? recommended_response_similarity = 1.0 # nil values as a perfect match - elsif !report1.recommended_response.empty? && !report2.recommended_response.empty? + elsif !report1.recommended_response.blank? && !report2.recommended_response.blank? recommended_response_similarity = text_similarity(report1.recommended_response, report2.recommended_response) else recommended_response_similarity = 0 @@ -209,13 +217,8 @@ def similarity_score(report1, report2) report2: report2 ) - if spam_report.save - puts "SpamReport created successfully." - else - puts "Failed to create SpamReport: #{spam_report.errors.full_messages.join(", ")}" - end - return (0.5 * description_similarity + 0.5 * recommended_response_similarity) + return (description_similarity + recommended_response_similarity)/2, spam_report end diff --git a/app/models/report.rb b/app/models/report.rb index 1593314e..11b69f90 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -52,7 +52,7 @@ class Report < ApplicationRecord scope :dismissed, lambda { where(dismissed: true) } scope :warned, lambda { where(warned: true) } scope :revoked, lambda { where(revoked: true) } - scope :unresolved, lambda { where("#{table_name}.dismissed IS FALSE AND #{table_name}.warned IS FALSE AND #{table_name}.revoked IS FALSE") } + scope :unresolved, lambda { where("#{table_name}.dismissed IS FALSE AND #{table_name}.warned IS FALSE AND #{table_name}.revoked IS FALSE AND #{table_name}.spam IS FALSE") } scope :watched, lambda { where(watched: true) } scope :search, lambda { |search| where("lower(reported_twitch_name) LIKE :search OR lower(reported_twitch_id) LIKE :search OR diff --git a/app/views/reports/_related_spam.html.erb b/app/views/reports/_related_spam.html.erb index 9a30e31c..9ded7cea 100644 --- a/app/views/reports/_related_spam.html.erb +++ b/app/views/reports/_related_spam.html.erb @@ -10,8 +10,7 @@ <% @spam_reports.each do |spam_report| %>
  • Description: <%= spam_report.description %>
    - Related as Report1 ID: <%= spam_report.report1_id %>
    - Related as Report2 ID: <%= spam_report.report2_id %> + -- Related as Reports: <%= spam_report.report1_id %> and <%= spam_report.report2_id %>
  • <% end %> From c777e800a954daed52a0ee29f399d55b2190233f Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Fri, 10 May 2024 12:15:39 -0400 Subject: [PATCH 23/26] fixed html --- app/controllers/reports_controller.rb | 2 +- app/models/report.rb | 6 +++++- app/views/reports/_related_spam.html.erb | 9 +++------ app/views/reports/_row.html.erb | 10 +++++++--- app/views/reports/_show_spam.html.erb | 1 + 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index ee294e01..4df0a7c0 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -209,7 +209,7 @@ def similarity_score(report1, report2) end # weighted avg of both where required weighs more - spam_info_value = "Description Simillarity of #{description_similarity} and Recommend Simillarity of #{recommended_response_similarity}" + spam_info_value = "Description Simillarity of #{description_similarity} and Recommend Simillarity of #{recommended_response_similarity} for #{report1.id} & #{report2.id}" spam_report = SpamReport.new( description: spam_info_value, diff --git a/app/models/report.rb b/app/models/report.rb index 11b69f90..44ad3ae4 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -110,8 +110,12 @@ def related_reports def related_spam_reports unless self.reported_twitch_id.blank? - SpamReport.where("report1_id = ? or report2_id = ?", self.id, self.id).distinct #unique + + # all related SpamReport records + SpamReport.where("report1_id = ? OR report2_id = ?", self.id, self.id).distinct + end + end protected diff --git a/app/views/reports/_related_spam.html.erb b/app/views/reports/_related_spam.html.erb index 9ded7cea..5e53094e 100644 --- a/app/views/reports/_related_spam.html.erb +++ b/app/views/reports/_related_spam.html.erb @@ -2,18 +2,15 @@
    - Related Report Spams <%= @spam_reports.present? ? "(#{@spam_reports.size})" : ''%> + Related Report Spams Descriptions <%= @spam_reports.present? ? "(#{@spam_reports.size})" : ''%>
    <% if @spam_reports.present? %>
    -
      <% @spam_reports.each do |spam_report| %> -
    • - Description: <%= spam_report.description %>
      - -- Related as Reports: <%= spam_report.report1_id %> and <%= spam_report.report2_id %> +  · 
    • + <%= spam_report.description %>
    • <% end %> -
    <% else %>
    diff --git a/app/views/reports/_row.html.erb b/app/views/reports/_row.html.erb index 36dc0f02..e8558ca5 100644 --- a/app/views/reports/_row.html.erb +++ b/app/views/reports/_row.html.erb @@ -8,9 +8,13 @@
    <% if report.unresolved? %> -
    - <%= l(report.created_at, format: "%b. %-d, %Y · %-l:%M%P ") %> -
    +
    + <%= l(report.created_at, format: "%b. %-d, %Y · %-l:%M%P ") %> +
    + <% elsif report.spam? %> +
    + (SPAM) <%= l(report.created_at, format: "%b. %-d, %Y · %-l:%M%P ") %> +
    <% elsif report.dismissed %>
    <%= l(report.updated_at, format: "%b. %-d, %Y") %> ·  diff --git a/app/views/reports/_show_spam.html.erb b/app/views/reports/_show_spam.html.erb index 279d73f6..9d723a2a 100644 --- a/app/views/reports/_show_spam.html.erb +++ b/app/views/reports/_show_spam.html.erb @@ -13,4 +13,5 @@ <%= render(partial: "reports/reporter_details") %> <%= render(partial: "reports/related_spam") %> +<%= render(partial: "reports/related_reports") %> <%= render(partial: "reports/comments")%> From 325f0d16c8926d0feefe91507b4fa93bdfe83594 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Fri, 10 May 2024 12:43:01 -0400 Subject: [PATCH 24/26] allow user to unspam or spam --- app/controllers/reports_controller.rb | 26 ++++++++++++++++++--- app/views/reports/_related_spam.html.erb | 2 +- app/views/reports/_show_spam.html.erb | 11 +++++++++ app/views/reports/_show_unresolved.html.erb | 2 ++ config/routes.rb | 2 ++ 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 4df0a7c0..89cfa898 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -7,9 +7,9 @@ class ReportsController < ApplicationController skip_before_action :verify_authenticity_token, only: [ :watch, :unwatch, :twitch_lookup ] - before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] - before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch ] - before_action :find_report, only: [ :show, :dismiss, :undismiss, :watch, :unwatch ] + before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch, :unspam, :spam] + before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch, :unspam, :spam] + before_action :find_report, only: [ :show, :dismiss, :undismiss, :watch, :unwatch, :unspam, :spam] after_action :check_report_matches, only: [ :create] around_action :display_timezone @@ -125,6 +125,26 @@ def unwatch end end end + + def spam + if @report.update(spam: true) + flash[:success] = "Report has been marked as spam." + else + flash[:error] = "Unable to update the report." + end + + redirect_to report_path(@report) + end + + def unspam + if @report.update(spam: false) + flash[:success] = "Report has been marked as not spam." + else + flash[:error] = "Unable to update the report." + end + redirect_to report_path(@report) + end + def twitch_lookup if params[:twitch_username].blank? diff --git a/app/views/reports/_related_spam.html.erb b/app/views/reports/_related_spam.html.erb index 5e53094e..440896bc 100644 --- a/app/views/reports/_related_spam.html.erb +++ b/app/views/reports/_related_spam.html.erb @@ -14,7 +14,7 @@
    <% else %>
    - No related badge abuse reports found. + No related automated spam description dectection.
    <% end %>
    diff --git a/app/views/reports/_show_spam.html.erb b/app/views/reports/_show_spam.html.erb index 9d723a2a..3b65d090 100644 --- a/app/views/reports/_show_spam.html.erb +++ b/app/views/reports/_show_spam.html.erb @@ -11,6 +11,17 @@ <%= render(partial: "reports/materials") %> <%= render(partial: "reports/associated_pledge") %> +
    + <% if @pledge %> + <%= link_to("Revoke", new_report_revocation_path(@report, back: request.fullpath), class: 'revoke-button button') %> + <%= link_to("Warn", new_report_warning_path(@report, back: request.fullpath), class: 'warn-button button') %> + <%= link_to("Unspam", new_report_warning_path(@report, back: request.fullpath), class: 'warn-button button') %> + <% end %> + <%= link_to("Dismiss", dismiss_report_path(@report), method: :post, class: 'dismiss-button button') %> + <%= link_to("Unspam", unspam_report_path(@report), method: :post, class: 'warn-button button') %> + <%= link_to('Back', params[:back].present? ? params[:back] : reports_path, class: "back-button mini button") %> +
    + <%= render(partial: "reports/reporter_details") %> <%= render(partial: "reports/related_spam") %> <%= render(partial: "reports/related_reports") %> diff --git a/app/views/reports/_show_unresolved.html.erb b/app/views/reports/_show_unresolved.html.erb index 061df200..d3bb7525 100644 --- a/app/views/reports/_show_unresolved.html.erb +++ b/app/views/reports/_show_unresolved.html.erb @@ -11,6 +11,8 @@ <%= render(partial: "reports/materials") %> <%= render(partial: "reports/associated_pledge") %>
    + <%= link_to("Spam", spam_report_path(@report), method: :post, class: 'revoke-button button') %> + <% if @pledge %> <%= link_to("Revoke", new_report_revocation_path(@report, back: request.fullpath), class: 'revoke-button button') %> <%= link_to("Warn", new_report_warning_path(@report, back: request.fullpath), class: 'warn-button button') %> diff --git a/config/routes.rb b/config/routes.rb index f2442d7a..37af190b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -32,6 +32,8 @@ post :undismiss post :watch post :unwatch + post :unspam + post :spam end resources :warnings, only: [ :new, :create ] resources :revocations, only: [ :new, :create ] From 519181ee1f34c2630e6ff489ad005dd16b7532c2 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Fri, 10 May 2024 13:08:25 -0400 Subject: [PATCH 25/26] fixed report twice --- app/controllers/reports_controller.rb | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 89cfa898..3d05ec56 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -10,7 +10,7 @@ class ReportsController < ApplicationController before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch, :unspam, :spam] before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss, :watch, :unwatch, :unspam, :spam] before_action :find_report, only: [ :show, :dismiss, :undismiss, :watch, :unwatch, :unspam, :spam] - after_action :check_report_matches, only: [ :create] + # after_action :check_report_matches, only: [ :create] around_action :display_timezone def index @@ -80,6 +80,8 @@ def create PledgeMailer.confirm_receipt(@report).deliver_now flash[:notice] = "You've successfully submitted the report. Thank you." + check_report_matches() + redirect_to root_path else flash.now[:alert] ||= "" @@ -195,15 +197,18 @@ def check_report_matches # mark as spam based on a similarity threshold spam_found = false potential_matches.find_each do |match| + if match != @report score, spam_report = similarity_score(@report, match) - if score >= 0.80 - if spam_report.save - match.update(spam: true, dismissed: false, warned: false, revoked: false, watched: false) - spam_found = true - puts "SpamReport created successfully." - else - puts "Failed to create SpamReport: #{spam_report.errors.full_messages.join(", ")}" + if score >= 0.70 + if spam_report.save + match.update(spam: true, dismissed: false, warned: false, revoked: false, watched: false) + spam_found = true + puts "SpamReport created successfully." + else + puts "Failed to create SpamReport: #{spam_report.errors.full_messages.join(", ")}" + end + end end @@ -231,6 +236,8 @@ def similarity_score(report1, report2) # weighted avg of both where required weighs more spam_info_value = "Description Simillarity of #{description_similarity} and Recommend Simillarity of #{recommended_response_similarity} for #{report1.id} & #{report2.id}" + # flash[:notice] << spam_info_value + spam_report = SpamReport.new( description: spam_info_value, report1: report1, From 996dffac4f681abed8e9c6f2ed65dec7e3b898e5 Mon Sep 17 00:00:00 2001 From: Paola Calle Date: Fri, 10 May 2024 15:16:40 -0400 Subject: [PATCH 26/26] forgot to update pledges and verifications --- app/controllers/pledges_controller.rb | 11 ++++++----- app/controllers/verifications_controller.rb | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/controllers/pledges_controller.rb b/app/controllers/pledges_controller.rb index dc887a02..2402397f 100644 --- a/app/controllers/pledges_controller.rb +++ b/app/controllers/pledges_controller.rb @@ -6,7 +6,6 @@ class PledgesController < ApplicationController before_action :ensure_staff, only: [ :index ] before_action :find_pledge, only: [ :show ] before_action :handle_twitch_auth, only: [ :new ] - before_action :apply_request_rate, only: [ :create] def index # f is used to filter reports by scope @@ -40,6 +39,12 @@ def new def create @pledge = Pledge.find_by(email: pledge_params[:email]) + # First check IP-based rate limiting + return unless limit_request_by_ip('pledge_create', new_pledge_path) + + # Fallback to device signature-based limiting (may not be needed) + return unless limit_request_by_signature('pledge_create', new_pledge_path) + if @pledge # Set cookie to enforce single visit to redirect page @@ -220,8 +225,4 @@ def pledge_params params.require(:pledge).permit(:first_name, :last_name, :email) end - def apply_request_rate - limit_create_request("pledges", new_pledge_path) - end - end diff --git a/app/controllers/verifications_controller.rb b/app/controllers/verifications_controller.rb index 4ac249bb..f8b0175a 100644 --- a/app/controllers/verifications_controller.rb +++ b/app/controllers/verifications_controller.rb @@ -10,7 +10,6 @@ class VerificationsController < ApplicationController :verify, :deny, :ignore, :withdraw, :voucher, :resend_cert, :watch, :unwatch ] before_action :find_verification, only: [ :show, :verify_eligibility, :deny_eligibility, :withdraw_eligibility, :verify, :deny, :ignore, :withdraw, :voucher, :resend_cert, :watch, :unwatch ] - before_action :apply_request_rate, only: [ :create] around_action :display_timezone @@ -50,6 +49,12 @@ def new def create @verification = Verification.new(verification_params) + # First check IP-based rate limiting + return unless limit_request_by_ip('verificate_create', new_verification_path) + + # Fallback to device signature-based limiting (may not be needed) + return unless limit_request_by_signature('verificate_create', new_verification_path) + if @verification.save # TODO: send notification to staff @@ -217,8 +222,4 @@ def verification_params params.require(:verification).permit(:first_name, :last_name, :email, :birth_date, :discord_username, :player_id_type, :player_id, :player_id_and_discord, :gender, :pronouns, :photo_id, :doctors_note, :social_profile, :voice_requested, :additional_notes) end - def apply_request_rate - limit_create_request("verification", new_verification_path) - end - end