Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gsoc'24): report as spam and moderation review page #25

Merged
merged 19 commits into from
Aug 18, 2024
Merged
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
6 changes: 2 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ gemspec
# used for dummy rails app integration
gem "devise"
gem "puma"
gem "sprockets-rails"

# testing against sqlite3 db
gem "sqlite3", "~> 1.7"

# testing
gem "appraisal"
gem "standardrb"
gem "font-awesome-sass", "~> 5.13.1"
gem "sqlite3"
gem "sprockets-rails"
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ By default, SimpleDiscussion will attempt to send email and slack notifications
SimpleDiscussion.setup do |config|
config.send_email_notifications = false # Default: true
config.send_slack_notifications = false # Default: true

config.markdown_circuit_embed = false # Default: false
config.markdown_user_tagging = false # Default: false
config.markdown_video_embed = false # Default false
end
```

Expand Down
6 changes: 6 additions & 0 deletions app/controllers/simple_discussion/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ def require_mod_or_author_for_thread!
end
end

def require_mod!
unless is_moderator?
redirect_to_root
end
end

private

def redirect_to_root
Expand Down
29 changes: 27 additions & 2 deletions app/controllers/simple_discussion/forum_posts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,22 @@ def update
end

def destroy
@forum_post.destroy!
redirect_to simple_discussion.forum_thread_path(@forum_thread)
# if @forum_post is first post of forum_thread then we need to destroy forum_thread
if @forum_thread.forum_posts.first == @forum_post
@forum_thread.destroy!
if params[:from] == "moderators_page"
redirect_to simple_discussion.spam_reports_forum_threads_path
else
redirect_to simple_discussion.root_path
end
else
@forum_post.destroy!
if params[:from] == "moderators_page"
redirect_to simple_discussion.spam_reports_forum_threads_path
else
redirect_to simple_discussion.forum_thread_path(@forum_thread)
end
end
end

def solved
Expand All @@ -52,6 +66,17 @@ def unsolved
redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: ActionView::RecordIdentifier.dom_id(@forum_post))
end

def report_post
@forum_post = @forum_thread.forum_posts.find(params[:id])
@spam_report = SpamReport.new(forum_post: @forum_post, user: current_user, reason: params[:reason], details: params[:details])

if @spam_report.save
redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: ActionView::RecordIdentifier.dom_id(@forum_post))
else
render template: "simple_discussion/forum_threads/show"
end
end

private

def set_forum_thread
Expand Down
6 changes: 6 additions & 0 deletions app/controllers/simple_discussion/forum_threads_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class SimpleDiscussion::ForumThreadsController < SimpleDiscussion::ApplicationCo
before_action :authenticate_user!, only: [:mine, :participating, :new, :create]
before_action :set_forum_thread, only: [:show, :edit, :update, :destroy]
before_action :require_mod_or_author_for_thread!, only: [:edit, :update, :destroy]
before_action :require_mod!, only: [:spam_reports]

def index
@forum_threads = ForumThread.pinned_first.sorted.includes(:user, :forum_category).paginate(page: page_number)
Expand All @@ -27,6 +28,11 @@ def participating
render action: :index
end

def spam_reports
@spam_reports = SpamReport.includes(:forum_post).paginate(page: page_number)
render action: :spam_reports
end

def show
@forum_post = ForumPost.new
@forum_post.user = current_user
Expand Down
1 change: 1 addition & 0 deletions app/models/forum_post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
class ForumPost < ApplicationRecord
belongs_to :forum_thread, counter_cache: true, touch: true
belongs_to :user
has_many :spam_reports, dependent: :destroy

validates :user_id, :body, presence: true
validate :clean_body, if: -> { SimpleDiscussion.profanity_filter }
Expand Down
15 changes: 15 additions & 0 deletions app/models/spam_report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class SpamReport < ApplicationRecord
belongs_to :forum_post
belongs_to :user

validates :forum_post_id, :user_id, :reason, presence: true
validates :details, presence: true, if: -> { reason == "others" }

enum reason: {
sexual_content: 0,
violent_content: 1,
irrelevant_content: 2,
misleading_content: 3,
others: 4
}
end
51 changes: 51 additions & 0 deletions app/views/layouts/simple_discussion.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
<%= t('.unanswered') %>
</div>
<% end %>
<% if is_moderator? %>
<%= forum_link_to simple_discussion.spam_reports_forum_threads_path do %>
<div class="btn-link">
<%= icon "fa-fw fa", "exclamation-triangle" %>
<%= t('.spam_posts') %>
</div>
<% end %>
<% end %>
</div>
<% if @forum_thread.present? && @forum_thread.persisted? %>
<div class="mt-3">
Expand All @@ -67,3 +75,46 @@
</div>

<% parent_layout("application") %>

<script type="module">
import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
window.Stimulus = Application.start()

Stimulus.register("dropdown", class extends Controller {
static targets = ["dropdownButton", "dropdownMenu"]

connect() {
this.dropdownButtonTarget.addEventListener("click", this.toggleDropdown.bind(this))
// if click somewhere else in the document, close the dropdown
window.addEventListener("click", (event) => {
if (!this.dropdownButtonTarget.contains(event.target)) {
this.dropdownMenuTarget.classList.remove("show")
}
})
}

// note that we are using bootstrap
toggleDropdown() {
this.dropdownMenuTarget.classList.toggle("show")
}
})


Stimulus.register("report-post", class extends Controller {
static targets = ["reportPostButton"]

connect() {
const reportPostForm = document.getElementById("reportPostForm")
const postId = this.element.dataset.postId
this.reportPostButtonTarget.addEventListener("click", () => {
const formActionArray = reportPostForm.action.split("/")
console.log(formActionArray)
if (formActionArray[formActionArray.length - 2] === "threads") {
reportPostForm.action += `/posts/${postId}/report_post`
} else {
reportPostForm.action = reportPostForm.action.replace(/\/\d+\//, `/${postId}/`)
}
})
}
})
</script>
76 changes: 37 additions & 39 deletions app/views/simple_discussion/forum_posts/_forum_post.html.erb
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
<%# We don't currently cache the forum posts because they have permissions to deal with %>

<%= content_tag :div, id: dom_id(forum_post), class: "forum-post" do %>
<div class="forum-post-header">
<% if is_moderator_or_owner?(forum_post) %>
<div class="float-right">
<%= link_to icon("fas","edit"), simple_discussion.edit_forum_thread_forum_post_path(@forum_thread, forum_post),
class: "text-muted",
data: { toggle: "tooltip", placement: "left" },
title: t('edit_this_post') %> &nbsp;
<%= link_to icon("fas","trash"), simple_discussion.forum_thread_forum_post_path(@forum_thread, forum_post),
class: "text-muted",
method: :delete,
data: { toggle: "tooltip", placement: "left", confirm: "Are you sure you want to delete this post?" },
title: t('delete_this_post') %>
</div>
<% end %>
<%= content_tag :div, id: forum_post.solved ? 'solution' : dom_id(forum_post), class: "forum-post" do %>
<div class="forum-post-header">
<div class="float-right d-flex align-items-center">
<% unless forum_post == @forum_thread.forum_posts.first %>
<% if is_moderator_or_owner?(@forum_thread) %>
<% if @forum_thread.solved? && forum_post.solved? %>
<%= link_to t('unmark_as_solution'), simple_discussion.unsolved_forum_thread_forum_post_path(@forum_thread, forum_post), method: :put, class: "badge badge-info p-2 mr-2" %>
<% else %>
<%= link_to t('mark_as_solution'), simple_discussion.solved_forum_thread_forum_post_path(@forum_thread, forum_post), method: :put, class: "badge badge-info p-2 mr-2" %>
<% end %>
<% end %>
<% end %>
<% if forum_post.solved? && forum_post != @forum_thread.forum_posts.first && !is_moderator_or_owner?(@forum_thread) %>
<span class="badge badge-success p-2 mr-2"><%= t('solution') %></span>
<% end %>
<% if user_signed_in? %>
<div class="dropdown" data-controller="dropdown">
<button class="btn btn-light bg-white" role="button" data-dropdown-target="dropdownButton">
<%= icon("fas","ellipsis-v") %>
</button>
<div class="dropdown-menu dropdown-menu-right" data-dropdown-target="dropdownMenu">
<% if is_moderator_or_owner?(forum_post) %>
<%= link_to t('edit_post'), simple_discussion.edit_forum_thread_forum_post_path(@forum_thread, forum_post),
class: "dropdown-item",
data: { toggle: "tooltip", placement: "left" },
title: t('edit_this_post') %>
<%= link_to t('delete_post'), simple_discussion.forum_thread_forum_post_path(@forum_thread, forum_post),
class: "dropdown-item",
method: :delete,
data: { toggle: "tooltip", placement: "left", confirm: "Are you sure you want to delete this post?" },
title: t('delete_this_post') %>
<% end %>
<%= link_to t('report_post'), "#", class: "dropdown-item", data: { controller: "report-post", report_post_target: "reportPostButton", toggle: "modal", target: "#reportPostModal", post_id: forum_post.id } %>
</div>
</div>
<% end %>
</div>
<div class="user-details d-flex flex-row">
<div class="user-avatar mr-2" ><img src="<%= gravatar_url_for(forum_post.user.email, size: 30) %>" alt="avatar of user" ></div>
<div class="details d-flex flex-column justify-content-between">
Expand All @@ -29,26 +49,4 @@
<div class="card-body p-3">
<%= formatted_content forum_post.body %>
</div>

<% if @forum_thread.solved? && forum_post.solved? %>
<div class="card-footer border-0 bg-transparent pt-0 mb-0">
<div class="pull-right">
<strong class="text-success"><%= icon("fas","check") %> <%= t('solved') %></strong>
<% if is_moderator_or_owner?(@forum_thread) %>
<%= link_to t('undo'), simple_discussion.unsolved_forum_thread_forum_post_path(@forum_thread, forum_post), class: "btn btn-sm btn-secondary text-white mx-2", method: :put %>
<% end %>
</div>
</div>

<% elsif is_moderator_or_owner?(@forum_thread) %>
<div class="card-footer border-0 bg-transparent pt-0 mb-0" style="background-color: #e1fff2; color: #4DDB9B; font-size: 1.05rem; font-weight: 500">
<div class="pull-right">
<%= link_to simple_discussion.solved_forum_thread_forum_post_path(@forum_thread, forum_post), method: :put do %>
<%= icon("fas", "check") %>&nbsp;&nbsp;
<%= t('mark_as_solved') %>
<% end %>
</div>
</div>
<% end %>

<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<div class="modal fade" id="reportPostModal" tabindex="-1" role="dialog" aria-labelledby="reportSpamModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="reportSpamModalLabel">Report Post as Spam</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<%= form_with id:"reportPostForm", url: simple_discussion.forum_thread_path(forum_thread), method: :post, local: true do |f| %>
<% if @spam_report && @spam_report.errors.any? %>
<div class="alert alert-danger" role="alert">
<% @spam_report.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</div>
<% end %>
<div class="form-group">
<%= f.collection_radio_buttons :reason, SpamReport.reasons.keys, :to_s, :humanize, include_hidden: false do |b| %>
<div class="form-check">
<%= b.radio_button(class: "form-check-input toggle-reason", required: true) %>
<%= b.label(class: "form-check-label") %>
</div>
<% end %>
</div>
<div class="form-group" id="details-group" style="display: none;">
<%= f.label :details, "Reason in detail (if others)" %>
<%= f.text_area :details, class: "form-control", rows: 2, id: "details-input" %>
<div class="invalid-feedback">Please provide details for 'Other' reason.</div>
</div>
<%= f.submit "Report", class: "btn btn-danger" %>
<% end %>
</div>
</div>
</div>
</div>

<script>
$(document).ready(function() {
const $detailsGroup = $('#details-group');
const $detailsInput = $('#details-input');
const $form = $('#reportPostForm');

$('.toggle-reason').change(function() {
if ($(this).val() === 'others') {
$detailsGroup.show();
$detailsInput.attr('required', true);
} else {
$detailsGroup.hide();
$detailsInput.removeAttr('required');
}
});

$form.on('submit', function(e) {
if ($('input[name="reason"]:checked').val() === 'others' && $detailsInput.val().trim() === '') {
e.preventDefault();
$detailsInput.addClass('is-invalid');
} else {
$detailsInput.removeClass('is-invalid');
}
});

$detailsInput.on('input', function() {
$(this).removeClass('is-invalid');
});
});
</script>
57 changes: 57 additions & 0 deletions app/views/simple_discussion/forum_posts/_spam_report.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<%= content_tag :tr, id: dom_id(spam_report), class: "forum-post" do %>
<td>
<div class="border rounded-lg">
<div class="forum-post-header">
<div class="user-details d-flex flex-row">
<div class="user-avatar mr-2" ><img src="<%= gravatar_url_for(spam_report.forum_post.user.email, size: 30) %>" alt="avatar of user" ></div>
<div class="details d-flex flex-column justify-content-between">
<p class="title">
<%= spam_report.forum_post.user.name %> <%= forum_user_badge(spam_report.forum_post.user) %>
</p>
<p class="subtitle"><%= t('on') %> &nbsp;<%= spam_report.forum_post.created_at.strftime("%b %d, %Y") %></p>
</div>
</div>
</div>

<div class="card-body p-3">
<%= formatted_content spam_report.forum_post.body %>
</div>
</div>
</td>

<td>
<div class="d-flex flex-row justify-content-between align-items-center p-2">
<% if spam_report.reason == "others" %>
Other:<%= spam_report.details %>
<% else %>
<%= spam_report.reason.humanize %>
<% end %>
</div>
</td>

<td>
<div class="d-flex flex-row justify-content-between align-items-center p-2">
<%= link_to spam_report.user.name, user_path(spam_report.user), class: "btn btn-outline-primary", title: t('user_profile') %>
</div>
</td>
<td>
<div class="d-flex flex-row justify-content-between align-items-center p-2">
<%= link_to simple_discussion.forum_thread_path(spam_report.forum_post.forum_thread, anchor: "forum_post_#{spam_report.forum_post.id}"),
class: "btn btn-dark",
data: { toggle: "tooltip", placement: "left" } do %>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-arrow-out-up-right"><path d="M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6"/><path d="m21 3-9 9"/><path d="M15 3h6v6"/></svg>
<% end %>
</div>
</td>
<td>
<div class="d-flex flex-row justify-content-between align-items-center p-2">
<%= link_to simple_discussion.forum_thread_forum_post_path(spam_report.forum_post.forum_thread, spam_report.forum_post, from: "moderators_page"),
method: :delete,
data: { toggle: "tooltip", placement: "left", confirm: "Are you sure you want to delete this post?" },
title: t('delete_this_post'),
class: "btn btn-danger" do %>
<i class="fas fa-trash-alt"></i>
<% end %>
</div>
</td>
<% end %>
Loading