diff --git a/app/assets/stylesheets/backstage.css b/app/assets/stylesheets/backstage.css index 4a97b3f2..7abfddef 100644 --- a/app/assets/stylesheets/backstage.css +++ b/app/assets/stylesheets/backstage.css @@ -17,7 +17,7 @@ border: solid 5px var(--black-color); -moz-box-sizing: border-box; -webkit-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } #backstage-container .panel-title { @@ -74,7 +74,7 @@ #backstage-container .panel-menu a:focus { cursor: pointer; - background-color: var(--bright-blue-color); + background-color: var(--bright-blue-color); outline: none; } @@ -92,7 +92,7 @@ #backstage-container .panel-filters a.selected { font-weight: 700; - color: var(--light-black-color); + color: var(--light-black-color); } #backstage-container .panel-filters a:hover { @@ -119,12 +119,21 @@ #backstage-container .record-row:hover { cursor: pointer; - background: var(--orange-overlay); + background: var(--orange-overlay); } #backstage-container .record-row:active { cursor: pointer; - background: var(--blue-overlay); + background: var(--blue-overlay); +} + +#backstage-container .record-row .icons-space { + width: 5.5em; +} + +#backstage-container .record-row .icons-space .icon { + font-family: var(--icon-font); + margin-right: 0.3em; } #backstage-container .record-row .row-title { @@ -160,12 +169,12 @@ width: auto; padding: 2em 2em 2em 2em; } - + #backstage-container .control-panel { min-width: 16em; padding: 2em 2em 2em 2em; } - + #backstage-container .panel-descriptor { text-align: left; } diff --git a/app/assets/stylesheets/reports.css b/app/assets/stylesheets/reports.css index 182a1cf4..ee5d7f15 100644 --- a/app/assets/stylesheets/reports.css +++ b/app/assets/stylesheets/reports.css @@ -54,6 +54,24 @@ font-size: 0.9em; } +#backstage-container .record-row .row-status { + font-weight: 500; + width: 7em; + text-align: right; +} + +#backstage-container .record-row .row-status.revoked { + color: var(--alert-red-color); +} + +#backstage-container .record-row .row-status.warned { + color: var(--bright-orange-color); +} + +#backstage-container .record-row .row-status.dismissed { + color: var(--bright-green-color); +} + #backstage-container .report-buttons { margin-top: 2em; } @@ -99,7 +117,7 @@ #backstage-container .report-buttons .button:focus { cursor: pointer; - background-color: var(--bright-blue-color); + background-color: var(--bright-blue-color); outline: none; } @@ -118,7 +136,7 @@ margin-left: 0em; margin-top: 2em; } - + #backstage-container .report-buttons a.stealth { margin-left: auto; margin-right: auto; diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 396dcacb..b0679e3d 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -1,58 +1,62 @@ class ReportsController < ApplicationController - + layout "backstage", only: [ :index, :show ] - + before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss ] before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss ] before_action :find_report, only: [ :show, :dismiss, :undismiss ] - before_action :find_reported_twitch_user, only: [ :show ] around_action :display_timezone - + def index # f is used to filter reports by scope if params[:f].present? && Report::AVAILABLE_SCOPES.key?(params[:f].to_sym) - @reports = eval("Report."+params[:f]+".all.order(created_at: :desc)") + @reports = eval("Report.includes(:pledge)."+params[:f]+".all.order(created_at: :desc)") # TODO add: paginate(page: params[:page], per_page: 30) @filter_category = params[:f] else - @reports = Report.unresolved.all.order(created_at: :desc) + @reports = Report.includes(:pledge).unresolved.all.order(created_at: :desc) @filter_category = "unresolved" end end - + def show # Create keybot advice message - if @reported_twitch_user == nil + if !@report.twitch_id @message = "The reported Twitch user does not exist." - elsif @pledge = Pledge.find_by(twitch_id: @reported_twitch_user) + elsif @pledge = Pledge.find_by(twitch_id: @report.twitch_id) @message = "The reported Twitch user signed the pledge as " + @pledge.twitch_display_name + " on " + @pledge.signed_on.strftime('%b. %-d, %Y.') else @message = "The reported Twitch user did not sign the pledge." end - + if @report.twitch_id + @other_reports = Report.where(twitch_id: @report.twitch_id).where.not(id: @report.id) + else + @other_reports = nil + end # TODO: check if reporter has pledged (lookup email/Twitch name) and add info to keybot message # TODO: check if incident stream owner has pledged (Twitch name) and add info to keybot message end - + def new @report = Report.new end def create @report = Report.new(report_params) - + if @report.save # Email notification to staff StaffMailer.notify_staff_new_report(@report).deliver_now - + set_twitch_id + flash[:notice] = "You've successfully submitted the report. Thank you." redirect_to root_path - else + else flash.now[:alert] ||= "" @report.errors.full_messages.each do |message| flash.now[:alert] << message + ". " - end + end render(action: :new) end end @@ -65,14 +69,14 @@ def dismiss end redirect_to reports_path end - + def undismiss @report.dismissed = false @report.reviewer = nil if @report.save flash[:notice] = "You undismissed the report about #{@report.reported_twitch_name}. It can now be reviewed again." redirect_to report_path(@report) - else + else redirect_to reports_path end end @@ -83,32 +87,32 @@ def find_report rescue ActiveRecord::RecordNotFound redirect_to staff_index_path end - - def find_reported_twitch_user + + def set_twitch_id # Check if reported_twitch_name exists on Twitch response = HTTParty.get(URI.escape("#{ENV['TWITCH_API_BASE_URL']}/users?login=#{@report.reported_twitch_name}"), headers: {"Client-ID": ENV['TWITCH_CLIENT_ID'], "Authorization": "Bearer #{TwitchToken.first.valid_token!}"}) - + if response["data"].blank? - @reported_twitch_user = nil + @report.update_attribute(:twitch_id, nil) else - @reported_twitch_user = response["data"][0]["id"] + @report.update_attribute(:twitch_id, response["data"][0]["id"]) end end - private + private def ensure_staff unless current_user.is_moderator? || current_user.is_admin? redirect_to root_url end end - + def display_timezone timezone = Time.find_zone( cookies[:browser_timezone] ) Time.use_zone(timezone) { yield } end - + def report_params params.require(:report).permit(:reporter_email, :reporter_twitch_name, :reported_twitch_name, :incident_stream, :incident_occurred, :incident_description, :recommended_response, :image) end - + end diff --git a/app/controllers/revocations_controller.rb b/app/controllers/revocations_controller.rb index fe7db4ed..f6a088b9 100644 --- a/app/controllers/revocations_controller.rb +++ b/app/controllers/revocations_controller.rb @@ -1,52 +1,51 @@ class RevocationsController < ApplicationController layout "backstage" - + before_action :authenticate_user! before_action :ensure_staff before_action :find_report before_action :ensure_sane_review - before_action :find_reported_twitch_user around_action :display_timezone - + def new - if @reported_twitch_user == nil + if @report.twitch_id == nil redirect_to staff_index_path - elsif @pledge = Pledge.find_by(twitch_id: @reported_twitch_user) + elsif @pledge = Pledge.find_by(twitch_id: @report.twitch_id) @revocation = Revocation.new else redirect_to staff_index_path end end - + def create - if @reported_twitch_user == nil + if @report.twitch_id == nil redirect_to staff_index_path - elsif @pledge = Pledge.find_by(twitch_id: @reported_twitch_user) + elsif @pledge = Pledge.find_by(twitch_id: @report.twitch_id) @revocation = Revocation.new(revocation_params) @revocation.report = @report @revocation.pledge = @pledge @revocation.reviewer = current_user - + if @revocation.save # Email revocation to pledger PledgeMailer.revoke_pledger(@revocation).deliver_now - + # Email reporter that action has been taken PledgeMailer.notify_reporter_revocation(@revocation).deliver_now - + # Revoke badge on Twitch # TODO: Roll over to Helix v6 API endpoint when they are built badge_result = HTTParty.delete(URI.escape("#{ENV['TWITCH_API_V5_BASE_URL']}/users/#{@pledge.twitch_id}/chat/badges/pledge?secret=#{ENV['TWITCH_PLEDGE_SECRET']}"), headers: {Accept: 'application/vnd.twitchtv.v5+json', "Client-ID": ENV['TWITCH_CLIENT_ID']}) - + @pledge.badge_revoked = true @pledge.revoked_on = Time.now @pledge.save - + @report.revoked = true @report.reviewer = current_user @report.save - + flash[:notice] = "You revoked the badge from #{@report.reported_twitch_name} and sent them a notification at #{@pledge.email}." redirect_to reports_path else @@ -60,24 +59,13 @@ def create redirect_to staff_index_path end end - + protected def find_report @report = Report.find(params[:report_id]) rescue ActiveRecord::RecordNotFound redirect_to staff_index_path end - - def find_reported_twitch_user - # Check if reported_twitch_name exists on Twitch - response = HTTParty.get(URI.escape("#{ENV['TWITCH_API_BASE_URL']}/users?login=#{@report.reported_twitch_name}"), headers: {"Client-ID": ENV['TWITCH_CLIENT_ID'], "Authorization": "Bearer #{TwitchToken.first.valid_token!}"}) - - if response["data"].blank? - @reported_twitch_user = nil - else - @reported_twitch_user = response["data"][0]["id"] - end - end private def ensure_staff @@ -85,24 +73,24 @@ def ensure_staff redirect_to root_url end end - + def ensure_sane_review unless !@report.dismissed && !@report.warned && !@report.revoked redirect_to staff_index_path end end - + def display_timezone timezone = Time.find_zone( cookies[:browser_timezone] ) Time.use_zone(timezone) { yield } end - + def conduct_warning_params params.require(:conduct_warning).permit(:reason) end - + def revocation_params params.require(:revocation).permit(:reason) end - + end diff --git a/app/controllers/warnings_controller.rb b/app/controllers/warnings_controller.rb index 37ca2b2b..53063b39 100644 --- a/app/controllers/warnings_controller.rb +++ b/app/controllers/warnings_controller.rb @@ -1,44 +1,43 @@ class WarningsController < ApplicationController - + layout "backstage" - + before_action :authenticate_user! before_action :ensure_staff before_action :find_report before_action :ensure_sane_review - before_action :find_reported_twitch_user around_action :display_timezone - + def new - if @reported_twitch_user == nil + if @report.twitch_id == nil redirect_to staff_index_path - elsif @pledge = Pledge.find_by(twitch_id: @reported_twitch_user) + elsif @pledge = Pledge.find_by(twitch_id: @report.twitch_id) @warning = ConductWarning.new else redirect_to staff_index_path end end - + def create - if @reported_twitch_user == nil + if @report.twitch_id == nil redirect_to staff_index_path - elsif @pledge = Pledge.find_by(twitch_id: @reported_twitch_user) + elsif @pledge = Pledge.find_by(twitch_id: @report.twitch_id) @warning = ConductWarning.new(conduct_warning_params) @warning.report = @report @warning.pledge = @pledge @warning.reviewer = current_user - + if @warning.save # Email warning to pledger PledgeMailer.warn_pledger(@warning).deliver_now - + # Email reporter that action has been taken PledgeMailer.notify_reporter_warning(@warning).deliver_now - + @report.warned = true @report.reviewer = current_user @report.save - + flash[:notice] = "You sent a warning to #{@pledge.email} (#{@report.reported_twitch_name})." redirect_to reports_path else @@ -59,17 +58,6 @@ def find_report rescue ActiveRecord::RecordNotFound redirect_to staff_index_path end - - def find_reported_twitch_user - # Check if reported_twitch_name exists on Twitch - response = HTTParty.get(URI.escape("#{ENV['TWITCH_API_BASE_URL']}/users?login=#{@report.reported_twitch_name}"), headers: {"Client-ID": ENV['TWITCH_CLIENT_ID'], "Authorization": "Bearer #{TwitchToken.first.valid_token!}"}) - - if response["data"].blank? - @reported_twitch_user = nil - else - @reported_twitch_user = response["data"][0]["id"] - end - end private def ensure_staff @@ -77,20 +65,20 @@ def ensure_staff redirect_to root_url end end - + def ensure_sane_review unless !@report.dismissed && !@report.warned && !@report.revoked redirect_to staff_index_path end end - + def display_timezone timezone = Time.find_zone( cookies[:browser_timezone] ) Time.use_zone(timezone) { yield } end - + def conduct_warning_params params.require(:conduct_warning).permit(:reason) end - + end diff --git a/app/models/pledge.rb b/app/models/pledge.rb index 6fae70c3..1ae65f68 100644 --- a/app/models/pledge.rb +++ b/app/models/pledge.rb @@ -1,7 +1,7 @@ class Pledge < ApplicationRecord - + before_create :ensure_signed_on_set - + validates_presence_of :first_name, :last_name, :email @@ -9,19 +9,20 @@ class Pledge < ApplicationRecord case_sensitive: false validates_format_of :email, with: /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/, - if: lambda { |x| x.email.present? } - - + if: lambda { |x| x.email.present? } + + belongs_to :referrer, class_name: :Pledge, foreign_key: :referrer_id, optional: true has_many :referrals, class_name: :Pledge, foreign_key: :referrer_id + has_many :reports, foreign_key: :twitch_id, primary_key: :twitch_id - # Non-sequential identifier scheme + # Non-sequential identifier scheme uniquify :identifier, length: 8, chars: ('A'..'Z').to_a + ('0'..'9').to_a def to_param identifier end - + def display_name if !self.twitch_display_name.blank? return self.twitch_display_name @@ -29,12 +30,12 @@ def display_name return self.first_name + ' ' + self.last_name.first + '.' end end - + private def ensure_signed_on_set if !self.signed_on.present? self.signed_on = Time.now end end - + end diff --git a/app/models/report.rb b/app/models/report.rb index a36a0acb..6e037c75 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -1,5 +1,5 @@ class Report < ApplicationRecord - + AVAILABLE_SCOPES = { unresolved: "Unresolved", dismissed: "Dismissed", @@ -7,12 +7,12 @@ class Report < ApplicationRecord revoked: "Revoked", all: "All" }.freeze - + IMAGE_STYLES = { thumb: { resize: "120x120" }, preview: { resize: "240x240" } }.freeze - + validates_presence_of :reported_twitch_name, :incident_stream, :incident_description, @@ -24,22 +24,23 @@ class Report < ApplicationRecord validates :incident_description, length: { maximum: 1000 } - + validates :recommended_response, length: { maximum: 500 } validate :ensure_sane_review belongs_to :reviewer, class_name: :User, foreign_key: :reviewer_id, optional: true - + belongs_to :pledge, counter_cache: true, foreign_key: :twitch_id, primary_key: :twitch_id, optional: true + image_accessor :image - + scope :dismissed, lambda { where("#{table_name}.dismissed IS TRUE") } scope :warned, lambda { where("#{table_name}.warned IS TRUE") } scope :revoked, lambda { where("#{table_name}.revoked IS TRUE") } scope :unresolved, lambda { where("#{table_name}.dismissed IS FALSE AND #{table_name}.warned IS FALSE AND #{table_name}.revoked IS FALSE") } - - + + def image_url(style = :thumb) if style == :original self.image.remote_url @@ -47,7 +48,7 @@ def image_url(style = :thumb) process_image(style).url end end - + protected def ensure_sane_review if (self.dismissed || self.warned || self.revoked) && !(self.dismissed ^ self.warned ^ self.revoked) @@ -62,10 +63,10 @@ def ensure_sane_review end end end - + private - def process_image(style) + def process_image(style) self.image.process(:auto_orient).thumb(Report::IMAGE_STYLES[style][:resize]) end - + end diff --git a/app/views/reports/_other_report_row.html.erb b/app/views/reports/_other_report_row.html.erb new file mode 100644 index 00000000..150f0196 --- /dev/null +++ b/app/views/reports/_other_report_row.html.erb @@ -0,0 +1,17 @@ +