diff --git a/app/assets/javascripts/app/people/activity.html.erb b/app/assets/javascripts/app/people/activity.html.erb index 9c5276bc2..802c2ce72 100644 --- a/app/assets/javascripts/app/people/activity.html.erb +++ b/app/assets/javascripts/app/people/activity.html.erb @@ -73,6 +73,39 @@
Edit Your Profile
+ +
+ + +
+
+ + +
+ +
+ + +
+ +
{{report.error}}
+ + +
+ +
+ Report submitted. +
+
diff --git a/app/assets/javascripts/app/people/activity.js b/app/assets/javascripts/app/people/activity.js index 502f111b0..c6ad872cb 100644 --- a/app/assets/javascripts/app/people/activity.js +++ b/app/assets/javascripts/app/people/activity.js @@ -18,4 +18,38 @@ angular.module('app').controller('PeopleShow', function ($scope, $routeParams, $ $scope.teams = teams; return teams; }); + + $scope.report = { + open: false, + submitting: false, + submitted: false, + error: null, + form: { + reason: 'spam', + comment: '' + } + }; + + $scope.toggleReportForm = function() { + $scope.report.open = !$scope.report.open; + $scope.report.error = null; + $scope.report.submitted = false; + }; + + $scope.submitReport = function() { + $scope.report.submitting = true; + $scope.report.error = null; + + $api.person_report($routeParams.id, $scope.report.form).then(function(response) { + $scope.report.submitting = false; + + if (response.meta && response.meta.success) { + $scope.report.submitted = true; + $scope.report.open = false; + $scope.report.form.comment = ''; + } else { + $scope.report.error = response.data && response.data.error || 'Unable to submit report.'; + } + }); + }; }); diff --git a/app/assets/javascripts/common/services/api.js.erb b/app/assets/javascripts/common/services/api.js.erb index b3e03abd8..57d0ad8c8 100644 --- a/app/assets/javascripts/common/services/api.js.erb +++ b/app/assets/javascripts/common/services/api.js.erb @@ -603,6 +603,10 @@ angular.module('services').config(function($httpProvider) { return this.call("/users/"+id); }; + this.person_report = function(id, data) { + return this.call("/people/"+id+"/report", "POST", data); + }; + this.person_update = function(data) { return this.call("/user", "PUT", data); }; diff --git a/app/controllers/api/v1/people_controller.rb b/app/controllers/api/v1/people_controller.rb index 664f93f9c..661b18aca 100644 --- a/app/controllers/api/v1/people_controller.rb +++ b/app/controllers/api/v1/people_controller.rb @@ -1,6 +1,8 @@ class Api::V1::PeopleController < ApplicationController before_action :require_auth, except: [:recent, :profile, :activity, :login, :create, :reset_password, :request_password_reset, :interesting, :count, :teams, :email_registered] - before_action :require_profile, only: [:profile, :activity, :teams] + before_action :require_profile, only: [:profile, :activity, :teams, :report] + + REPORT_REASONS = %w(spam abuse fraud other).freeze # show all of the authenticated user's info def show @@ -18,6 +20,30 @@ def show def profile end + def report + require_params(:reason) + + reason = params[:reason].to_s + unless REPORT_REASONS.include?(reason) + render json: { error: "Reason must be one of: #{REPORT_REASONS.join(', ')}" }, status: :unprocessable_entity + return + end + + if @profile_person == @person + render json: { error: "You cannot report your own profile" }, status: :unprocessable_entity + return + end + + Mailer.profile_reported( + reporter: @person, + reported_person: @profile_person, + reason: reason, + comment: params[:comment].to_s + ).deliver + + render json: { message: "Profile report submitted" }, status: :ok + end + # get info about the user's account def account # TODO exclude promotional money @@ -362,7 +388,8 @@ def render_login_rabl end def require_profile - @profile_person = Person.where(id: params[:profile_id]).first + profile_id = params[:profile_id].to_s.split('-', 2).first + @profile_person = Person.where(id: profile_id).first unless @profile_person render json: { error: 'Profile not found' }, status: :not_found diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index 02176b900..5851d6257 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -634,6 +634,24 @@ def extension_feedback(options) end end + def profile_reported(options) + @reporter = options.fetch(:reporter) + @reported_person = options.fetch(:reported_person) + @reason = options.fetch(:reason) + @comment = options[:comment].to_s + @reported_profile_url = "#{Api::Application.config.www_url}people/#{@reported_person.to_param}" + + mail( + to: "support@bountysource.com", + from: @reporter.email, + reply_to: @reporter.email, + subject: "Profile reported: #{@reported_person.display_name}" + ) do |format| + format.text + format.html + end + end + # def newsletter(options) # @person = options[:person] # diff --git a/app/views/mailer/profile_reported.html.erb b/app/views/mailer/profile_reported.html.erb new file mode 100644 index 000000000..c9393e524 --- /dev/null +++ b/app/views/mailer/profile_reported.html.erb @@ -0,0 +1,34 @@ +

Profile reported

+ +

+ Reported profile: + <%= @reported_person.display_name %> +

+ +

+ Profile URL: + <%= link_to @reported_profile_url, @reported_profile_url %> +

+ +

+ Reporter: + <%= @reporter.display_name %> +

+ +

+ Reporter email: + <%= mail_to @reporter.email %> +

+ +

+ Reason: + <%= @reason %> +

+ +

+ Comment: +

+ +

+ <%= simple_format(@comment.presence || "No comment provided.") %> +

diff --git a/app/views/mailer/profile_reported.text.erb b/app/views/mailer/profile_reported.text.erb new file mode 100644 index 000000000..a7bcbb4fd --- /dev/null +++ b/app/views/mailer/profile_reported.text.erb @@ -0,0 +1,12 @@ +Profile reported + +Reported profile: <%= @reported_person.display_name %> +Profile URL: <%= @reported_profile_url %> + +Reporter: <%= @reporter.display_name %> +Reporter email: <%= @reporter.email %> + +Reason: <%= @reason %> + +Comment: +<%= @comment.presence || "No comment provided." %> diff --git a/config/routes.rb b/config/routes.rb index 780d7ee9a..8028ea4a4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -271,6 +271,7 @@ scope controller: :people do get 'users/:profile_id', action: :profile get 'users/:profile_id/activity', action: :activity + post 'people/:profile_id/report', action: :report get 'people/count', action: :count get 'people/:profile_id/teams', action: :teams get 'people/:profile_id/activity', action: :activity diff --git a/spec/controllers/api/v1/people_controller_spec.rb b/spec/controllers/api/v1/people_controller_spec.rb index bb54dac27..626bbcfa0 100644 --- a/spec/controllers/api/v1/people_controller_spec.rb +++ b/spec/controllers/api/v1/people_controller_spec.rb @@ -102,4 +102,52 @@ expect(person.languages).to include language2 end end + + describe "report" do + let(:reporter) { create(:person, email: "reporter@example.com", display_name: "Reporter") } + let(:reported_person) { create(:person, email: "reported@example.com", display_name: "Reported") } + let(:params) { { access_token: reporter.create_access_token, profile_id: reported_person.to_param } } + + it "emails support with the selected reason and comment" do + expect { + post :report, params: params.merge(reason: "spam", comment: "Suspicious links") + }.to change(ActionMailer::Base.deliveries, :count).by(1) + + assert_response :ok + + email = ActionMailer::Base.deliveries.last + expect(email.to).to eq(["support@bountysource.com"]) + expect(email.from).to eq([reporter.email]) + expect(email.reply_to).to eq([reporter.email]) + expect(email.subject).to eq("Profile reported: #{reported_person.display_name}") + expect(email.encoded).to include("spam") + expect(email.encoded).to include("Suspicious links") + expect(email.encoded).to include(reported_person.to_param) + end + + it "requires authentication" do + post :report, params: params.except(:access_token).merge(reason: "spam") + assert_response :unauthorized + end + + it "rejects invalid reasons" do + expect { + post :report, params: params.merge(reason: "not-a-reason") + }.not_to change(ActionMailer::Base.deliveries, :count) + + assert_response :unprocessable_entity + end + + it "does not allow self reports" do + expect { + post :report, params: { + access_token: reporter.create_access_token, + profile_id: reporter.to_param, + reason: "spam" + } + }.not_to change(ActionMailer::Base.deliveries, :count) + + assert_response :unprocessable_entity + end + end end diff --git a/spec/mailers/mailer_spec.rb b/spec/mailers/mailer_spec.rb index c305dae87..03ca4618d 100644 --- a/spec/mailers/mailer_spec.rb +++ b/spec/mailers/mailer_spec.rb @@ -96,4 +96,26 @@ expect(email.subject).to eq("#{fundraiser.person.display_name} backed your fundraiser #{fundraiser.title}") end end + + describe "#profile_reported" do + let(:reported_person) { create(:person, display_name: "Reported Person") } + let(:email) do + Mailer.profile_reported( + reporter: person, + reported_person: reported_person, + reason: "fraud", + comment: "Looks suspicious" + ) + end + + it "renders a support email from the reporter" do + expect(email.to).to eq(["support@bountysource.com"]) + expect(email.from).to eq([person.email]) + expect(email.reply_to).to eq([person.email]) + expect(email.subject).to eq("Profile reported: #{reported_person.display_name}") + expect(email.encoded).to include("fraud") + expect(email.encoded).to include("Looks suspicious") + expect(email.encoded).to include(reported_person.to_param) + end + end end