Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions app/assets/javascripts/app/people/activity.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,39 @@
<div ng-show="current_person.id == person.id" class="text-center">
<a href="/settings" class="btn btn-default btn-long border-gunmetal">Edit Your Profile</a>
</div>

<div ng-show="current_person && current_person.id != person.id" class="mt-3">
<button type="button" class="btn btn-default btn-long border-gunmetal center-block" ng-click="toggleReportForm()">
Report this profile
</button>

<form ng-show="report.open" class="mt-3" ng-submit="submitReport()">
<div class="form-group">
<label for="profile-report-reason">Reason</label>
<select id="profile-report-reason" class="form-control" ng-model="report.form.reason">
<option value="spam">Spam</option>
<option value="abuse">Abuse</option>
<option value="fraud">Fraud</option>
<option value="other">Other</option>
</select>
</div>

<div class="form-group">
<label for="profile-report-comment">Comment</label>
<textarea id="profile-report-comment" class="form-control" rows="3" maxlength="1000" ng-model="report.form.comment" placeholder="Optional details"></textarea>
</div>

<div class="alert alert-danger" ng-show="report.error">{{report.error}}</div>

<button type="submit" class="btn btn-primary btn-long" ng-disabled="report.submitting">
{{report.submitting ? 'Submitting...' : 'Submit report'}}
</button>
</form>

<div class="alert alert-success mt-3" ng-show="report.submitted">
Report submitted.
</div>
</div>
</div>
</div>
</div>
Expand Down
34 changes: 34 additions & 0 deletions app/assets/javascripts/app/people/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.';
}
});
};
});
4 changes: 4 additions & 0 deletions app/assets/javascripts/common/services/api.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down
31 changes: 29 additions & 2 deletions app/controllers/api/v1/people_controller.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions app/mailers/mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
#
Expand Down
34 changes: 34 additions & 0 deletions app/views/mailer/profile_reported.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<h1>Profile reported</h1>

<p>
<strong>Reported profile:</strong>
<%= @reported_person.display_name %>
</p>

<p>
<strong>Profile URL:</strong>
<%= link_to @reported_profile_url, @reported_profile_url %>
</p>

<p>
<strong>Reporter:</strong>
<%= @reporter.display_name %>
</p>

<p>
<strong>Reporter email:</strong>
<%= mail_to @reporter.email %>
</p>

<p>
<strong>Reason:</strong>
<%= @reason %>
</p>

<p>
<strong>Comment:</strong>
</p>

<p>
<%= simple_format(@comment.presence || "No comment provided.") %>
</p>
12 changes: 12 additions & 0 deletions app/views/mailer/profile_reported.text.erb
Original file line number Diff line number Diff line change
@@ -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." %>
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions spec/controllers/api/v1/people_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 22 additions & 0 deletions spec/mailers/mailer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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