diff --git a/Gemfile b/Gemfile index ae8dea2..5797428 100644 --- a/Gemfile +++ b/Gemfile @@ -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" diff --git a/README.md b/README.md index 0433edc..e07c58b 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/app/assets/stylesheets/simple_discussion.scss b/app/assets/stylesheets/simple_discussion.scss index c63f794..b82fb80 100644 --- a/app/assets/stylesheets/simple_discussion.scss +++ b/app/assets/stylesheets/simple_discussion.scss @@ -248,8 +248,13 @@ border: 1px solid #80808029; .card-body { + overflow-x: auto; margin-top: 16px; } + .card-body iframe { + border-radius: $post-body-border-radius; + border: 2px solid #80808029; + } } // Formatting the listtile for user details @@ -296,3 +301,41 @@ .thread-page-container { padding: 24px; } + +.preview::before { + content: "Preview"; + width: 80px; +} + +p { + font-size: 18px; +} + +blockquote { + border-left: 5px solid #e9ecef; + padding: 10px 20px; + margin: 10px 0; + font-size: 18px; + font-weight: 500; + color: #6c757d; +} + +.CodeMirror-line span { + font-size: 18px; +} + +.simple_discussion .leaderboard .user-avatar { + border-radius: 50%; + width: 56px; + height: 56px; + overflow: hidden; + img { + width: 100%; + height: 100%; + object-fit: cover; + } +} + +.simple_discussion .leaderboard { + margin-top: 24px; +} diff --git a/app/controllers/simple_discussion/application_controller.rb b/app/controllers/simple_discussion/application_controller.rb index e464f26..4d1a8cc 100644 --- a/app/controllers/simple_discussion/application_controller.rb +++ b/app/controllers/simple_discussion/application_controller.rb @@ -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 diff --git a/app/controllers/simple_discussion/forum_posts_controller.rb b/app/controllers/simple_discussion/forum_posts_controller.rb index 23e59e9..9507ac2 100644 --- a/app/controllers/simple_discussion/forum_posts_controller.rb +++ b/app/controllers/simple_discussion/forum_posts_controller.rb @@ -5,15 +5,26 @@ class SimpleDiscussion::ForumPostsController < SimpleDiscussion::ApplicationCont before_action :require_mod_or_author_for_post!, only: [:edit, :update, :destroy] before_action :require_mod_or_author_for_thread!, only: [:solved, :unsolved] + POINTS = { + create_post: 10, # on forum post creation + delete_post: -10, # on forum post deletion + marked_as_solution: 100, # if forum thread author/moderator marked the post as solved + unmarked_as_solution: -100, # undoing the marked as solution + delete_reported_post: -100 # if moderator deletes the post hence it is spam post + } + def create @forum_post = @forum_thread.forum_posts.new(forum_post_params) @forum_post.user_id = current_user.id - if @forum_post.save - SimpleDiscussion::ForumPostNotificationJob.perform_later(@forum_post) - redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: "forum_post_#{@forum_post.id}") - else - render template: "simple_discussion/forum_threads/show", status: :unprocessable_entity + ActiveRecord::Base.transaction do + if @forum_post.save + update_leaderboard(current_user, POINTS[:create_post]) + SimpleDiscussion::ForumPostNotificationJob.perform_later(@forum_post) + redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: "forum_post_#{@forum_post.id}") + else + render template: "simple_discussion/forum_threads/show", status: :unprocessable_entity + end end end @@ -29,29 +40,73 @@ 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 + is_first_post = @forum_thread.forum_posts.first == @forum_post + + ActiveRecord::Base.transaction do + if is_first_post + @forum_thread.destroy! + else + @forum_post.destroy! + end + + # leaderboard points distribution + if is_moderator? && (@forum_post.user != current_user) + update_leaderboard(@forum_post.user, POINTS[:delete_reported_post]) + # further we can distribute points if needed to the user who reported the post + + # spam_report = SpamReport.find_by(forum_post: @forum_post) + # update_leaderboard(spam_report.user, POINTS[:report_spam]) if spam_report + else + update_leaderboard(@forum_post.user, POINTS[:delete_post]) + end + end + + redirect_to redirect_after_delete_path(is_first_post) end def solved - @forum_post = @forum_thread.forum_posts.find(params[:id]) + ActiveRecord::Base.transaction do + @forum_post = @forum_thread.forum_posts.find(params[:id]) + + # update the previously solved post's author's leaderboard points + previously_solved_posts = @forum_thread.forum_posts.where(solved: true) + previously_solved_posts.each do |post| + update_user_leaderboard(post.user, POINTS[:unmarked_as_solution]) + end - @forum_thread.forum_posts.update_all(solved: false) - @forum_post.update_column(:solved, true) - @forum_thread.update_column(:solved, true) + # update the current post's author leaderboard points + update_leaderboard(@forum_post.user, POINTS[:marked_as_solution]) + @forum_thread.forum_posts.update_all(solved: false) + @forum_post.update_column(:solved, true) + @forum_thread.update_column(:solved, true) + end redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: ActionView::RecordIdentifier.dom_id(@forum_post)) end def unsolved - @forum_post = @forum_thread.forum_posts.find(params[:id]) - - @forum_thread.forum_posts.update_all(solved: false) - @forum_thread.update_column(:solved, false) + ActiveRecord::Base.transaction do + @forum_post = @forum_thread.forum_posts.find(params[:id]) + update_leaderboard(@forum_post.user, POINTS[:unmarked_as_solution]) + @forum_thread.forum_posts.update_all(solved: false) + @forum_thread.update_column(:solved, false) + end redirect_to simple_discussion.forum_thread_path(@forum_thread, anchor: ActionView::RecordIdentifier.dom_id(@forum_post)) end + def report_spam + @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 @@ -69,4 +124,20 @@ def set_forum_post def forum_post_params params.require(:forum_post).permit(:body) end + + def update_leaderboard(user, points) + leaderboard = user.forum_leaderboard || user.build_forum_leaderboard + leaderboard.points += points + leaderboard.save! + end + + def redirect_after_delete_path(is_first_post) + if params[:from] == "moderators_page" + simple_discussion.spam_reports_forum_threads_path + elsif is_first_post + simple_discussion.root_path + else + simple_discussion.forum_thread_path(@forum_thread) + end + end end diff --git a/app/controllers/simple_discussion/forum_threads_controller.rb b/app/controllers/simple_discussion/forum_threads_controller.rb index 570597a..eb8d981 100644 --- a/app/controllers/simple_discussion/forum_threads_controller.rb +++ b/app/controllers/simple_discussion/forum_threads_controller.rb @@ -2,6 +2,13 @@ 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] + + POINTS = { + create_thread: 20, # on forum thread creation + delete_thread: -20, # on forum thread deletion + delete_reported_thread_by_moderator: -100 # if moderator deletes the thread hence it is spam post + } def index @forum_threads = ForumThread.pinned_first.sorted.includes(:user, :forum_category).paginate(page: page_number) @@ -27,6 +34,15 @@ 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 leaderboard + @ranked_users = ForumLeaderboard.order(points: :desc).paginate(page: page_number) + end + def show @forum_post = ForumPost.new @forum_post.user = current_user @@ -41,11 +57,14 @@ def create @forum_thread = current_user.forum_threads.new(forum_thread_params) @forum_thread.forum_posts.each { |post| post.user_id = current_user.id } - if @forum_thread.save - SimpleDiscussion::ForumThreadNotificationJob.perform_later(@forum_thread) - redirect_to simple_discussion.forum_thread_path(@forum_thread) - else - render action: :new, status: :unprocessable_entity + ActiveRecord::Base.transaction do + if @forum_thread.save + update_leaderboard(current_user, POINTS[:create_thread]) + SimpleDiscussion::ForumThreadNotificationJob.perform_later(@forum_thread) + redirect_to simple_discussion.forum_thread_path(@forum_thread) + else + render action: :new, status: :unprocessable_entity + end end end @@ -61,7 +80,15 @@ def update end def destroy - @forum_thread.destroy! + ActiveRecord::Base.transaction do + @forum_thread.destroy! + if is_moderator? && (@forum_thread.user != current_user) + update_leaderboard(@forum_thread.user, POINTS[:delete_reported_thread_by_moderator]) + else + update_leaderboard(@forum_thread.user, POINTS[:delete_thread]) + end + end + redirect_to simple_discussion.forum_threads_path end @@ -74,4 +101,10 @@ def set_forum_thread def forum_thread_params params.require(:forum_thread).permit(:title, :forum_category_id, forum_posts_attributes: [:body]) end + + def update_leaderboard(user, points) + leaderboard = user.forum_leaderboard || user.build_forum_leaderboard + leaderboard.points += points + leaderboard.save! + end end diff --git a/app/helpers/simple_discussion/forum_posts_helper.rb b/app/helpers/simple_discussion/forum_posts_helper.rb index c63c4a6..f642614 100644 --- a/app/helpers/simple_discussion/forum_posts_helper.rb +++ b/app/helpers/simple_discussion/forum_posts_helper.rb @@ -1,3 +1,46 @@ +require "redcarpet" +class CustomRenderer < Redcarpet::Render::HTML + def initialize(circuit_embed: false, video_embed: false, user_tagging: false) + @circuit_embed = circuit_embed + @video_embed = video_embed + @user_tagging = user_tagging + super() + end + + def image(url, title, alt_text) + case alt_text + when "Circuit" + if @circuit_embed + "
" + else + "\"#{alt_text}\"
" + end + when "Video" + if @video_embed + video_id = url.split("v=")[1].split("&")[0] + "
" + else + "\"#{alt_text}\"
" + end + else + # default image rendering + "\"#{alt_text}\"
" + end + end + + def link(link, _title, content) + if @user_tagging && link.start_with?("/users/") + uri = URI.parse(link) + uri.path =~ %r{^/users/\d+/?$} + # remove the brackets from the content + content = content.gsub(/[()]/, "") + "#{content}" + else + "#{content}" + end + end +end + module SimpleDiscussion::ForumPostsHelper def category_link(category) link_to category.name, simple_discussion.forum_category_forum_threads_path(category), @@ -6,7 +49,20 @@ def category_link(category) # Override this method to provide your own content formatting like Markdown def formatted_content(text) - simple_format(text) + options = { + hard_wrap: true, + filter_html: true, + autolink: true, + tables: true + } + + renderer = CustomRenderer.new( + circuit_embed: SimpleDiscussion.markdown_circuit_embed, + video_embed: SimpleDiscussion.markdown_video_embed, + user_tagging: SimpleDiscussion.markdown_user_tagging + ) + markdown = Redcarpet::Markdown.new(renderer, options) + markdown.render(text).html_safe end def forum_post_classes(forum_post) diff --git a/app/models/forum_leaderboard.rb b/app/models/forum_leaderboard.rb new file mode 100644 index 0000000..c7aa296 --- /dev/null +++ b/app/models/forum_leaderboard.rb @@ -0,0 +1,3 @@ +class ForumLeaderboard < ApplicationRecord + belongs_to :user +end diff --git a/app/models/forum_post.rb b/app/models/forum_post.rb index dfd9202..80ccced 100644 --- a/app/models/forum_post.rb +++ b/app/models/forum_post.rb @@ -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 } diff --git a/app/models/spam_report.rb b/app/models/spam_report.rb new file mode 100644 index 0000000..bc9b113 --- /dev/null +++ b/app/models/spam_report.rb @@ -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 diff --git a/app/views/layouts/simple_discussion.html.erb b/app/views/layouts/simple_discussion.html.erb index 80f1e73..b98ec6e 100644 --- a/app/views/layouts/simple_discussion.html.erb +++ b/app/views/layouts/simple_discussion.html.erb @@ -41,6 +41,22 @@ <%= t('.unanswered') %> <% end %> + + <%= forum_link_to simple_discussion.leaderboard_forum_threads_path do %> + + <% end %> + + <% if is_moderator? %> + <%= forum_link_to simple_discussion.spam_reports_forum_threads_path do %> + + <% end %> + <% end %> <% if @forum_thread.present? && @forum_thread.persisted? %>
@@ -67,3 +83,295 @@
<% parent_layout("application") %> + + + + + + + diff --git a/app/views/simple_discussion/forum_posts/_form.html.erb b/app/views/simple_discussion/forum_posts/_form.html.erb index 1a99524..c982866 100644 --- a/app/views/simple_discussion/forum_posts/_form.html.erb +++ b/app/views/simple_discussion/forum_posts/_form.html.erb @@ -1,7 +1,14 @@
<%= form_for [@forum_thread, @forum_post], url: (@forum_post.persisted? ? simple_discussion.forum_thread_forum_post_path(@forum_thread, @forum_post) : simple_discussion.forum_thread_forum_posts_path(@forum_thread)), - html: { data: { behavior: "comment-form" } } do |f| %> + html: { + data: { + controller: "simplemde", + simplemde_circuit_embed_value: SimpleDiscussion.markdown_circuit_embed, + simplemde_user_tagging_value: SimpleDiscussion.markdown_user_tagging, + simplemde_video_embed_value: SimpleDiscussion.markdown_video_embed, + } + } do |f| %> <% if @forum_post.errors.any? %> <% end %> -
-
- <%= f.text_area :body, placeholder: t('add_a_comment'), rows: 8, class: "form-control simplemde", data: { behavior: "comment-body" } %> +
+
+ <%= f.text_area :body, placeholder: t('add_a_comment'), rows: 8, class: "form-control", data: { simplemde_target: "textarea" } %> +
+ <%= f.button "#{f.object.new_record? ? t('comment') : t('update_comment')}", class: "btn forum-primary-btn", style: "bottom: 10px; right: 10px", data: { disable_with: " #{t('saving_comment')}" } %>
- <%= f.button "#{f.object.new_record? ? t('comment') : t('update_comment') }", class: "btn forum-primary-btn", style: "bottom: 10px; right: 10px", data: {disable_with: " #{t('saving_comment')}"} %> -
- - <%# Describe text formatting options here with a link %> - <%#= link_to "Parsed with Markdown", "https://guides.github.com/features/mastering-markdown/", target: "_blank" %> + + <% end %> + + + + + + diff --git a/app/views/simple_discussion/forum_posts/_forum_post.html.erb b/app/views/simple_discussion/forum_posts/_forum_post.html.erb index 9f2964f..3655b67 100644 --- a/app/views/simple_discussion/forum_posts/_forum_post.html.erb +++ b/app/views/simple_discussion/forum_posts/_forum_post.html.erb @@ -1,20 +1,38 @@ -<%# 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 %> -
- <% if is_moderator_or_owner?(forum_post) %> -
- <%= 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') %>   - <%= 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') %> -
- <% end %> +<%= content_tag :div, id: forum_post.solved ? 'solution' : dom_id(forum_post), class: "forum-post" do %> +
+
+ <% if forum_post.solved? %> + <%= t('solution') %> + <% end %> + <% if user_signed_in? %> + + <% end %> +
avatar of user
@@ -29,26 +47,4 @@
<%= formatted_content forum_post.body %>
- - <% if @forum_thread.solved? && forum_post.solved? %> - - - <% elsif is_moderator_or_owner?(@forum_thread) %> - - <% end %> - <% end %> diff --git a/app/views/simple_discussion/forum_posts/_report_spam_modal_form.html.erb b/app/views/simple_discussion/forum_posts/_report_spam_modal_form.html.erb new file mode 100644 index 0000000..2b64a44 --- /dev/null +++ b/app/views/simple_discussion/forum_posts/_report_spam_modal_form.html.erb @@ -0,0 +1,37 @@ + diff --git a/app/views/simple_discussion/forum_posts/_spam_report.html.erb b/app/views/simple_discussion/forum_posts/_spam_report.html.erb new file mode 100644 index 0000000..a98c39b --- /dev/null +++ b/app/views/simple_discussion/forum_posts/_spam_report.html.erb @@ -0,0 +1,57 @@ +<%= content_tag :tr, id: dom_id(spam_report), class: "forum-post" do %> + +
+
+
+
avatar of user
+
+

+ <%= spam_report.forum_post.user.name %> <%= forum_user_badge(spam_report.forum_post.user) %> +

+

<%= t('on') %>  <%= spam_report.forum_post.created_at.strftime("%b %d, %Y") %>

+
+
+
+ +
+ <%= formatted_content spam_report.forum_post.body %> +
+
+ + + +
+ <% if spam_report.reason == "others" %> + <%= spam_report.details %> + <% else %> + <%= spam_report.reason.humanize %> + <% end %> +
+ + + +
+ <%= link_to spam_report.user.name, user_profile_link(spam_report.user), class: "btn btn-outline-primary", title: t('user_profile') %> +
+ + +
+ <%= 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 %> + + <% end %> +
+ + +
+ <%= 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 %> + + <% end %> +
+ +<% end %> diff --git a/app/views/simple_discussion/forum_posts/edit.html.erb b/app/views/simple_discussion/forum_posts/edit.html.erb index b7ea600..f529b3f 100644 --- a/app/views/simple_discussion/forum_posts/edit.html.erb +++ b/app/views/simple_discussion/forum_posts/edit.html.erb @@ -25,7 +25,5 @@
-
- <%= render "form" %> -
+ <%= render "form" %> <% end %> diff --git a/app/views/simple_discussion/forum_threads/_form.html.erb b/app/views/simple_discussion/forum_threads/_form.html.erb index 64f2d18..cbf9318 100644 --- a/app/views/simple_discussion/forum_threads/_form.html.erb +++ b/app/views/simple_discussion/forum_threads/_form.html.erb @@ -1,6 +1,14 @@ <%= form_for @forum_thread, url: (@forum_thread.persisted? ? simple_discussion.forum_thread_path(@forum_thread) : simple_discussion.forum_threads_path), - html: { data: {behavior: "comment-form"} } do |f| %> + html: { + data: { + behavior: "comment-form", + controller: "simplemde", + simplemde_circuit_embed_value: SimpleDiscussion.markdown_circuit_embed, + simplemde_user_tagging_value: SimpleDiscussion.markdown_user_tagging, + simplemde_video_embed_value: SimpleDiscussion.markdown_video_embed, + } + } do |f| %> <% if @forum_thread.errors.any? %> + + <% end %> + + + + + + diff --git a/app/views/simple_discussion/forum_threads/leaderboard.html.erb b/app/views/simple_discussion/forum_threads/leaderboard.html.erb new file mode 100644 index 0000000..07c6588 --- /dev/null +++ b/app/views/simple_discussion/forum_threads/leaderboard.html.erb @@ -0,0 +1,20 @@ +

Leaderboard

+ +
+ <%= will_paginate @ranked_users, url_builder: simple_discussion, renderer: SimpleDiscussion::BootstrapLinkRenderer %> +
diff --git a/app/views/simple_discussion/forum_threads/show.html.erb b/app/views/simple_discussion/forum_threads/show.html.erb index 9534310..84ec509 100644 --- a/app/views/simple_discussion/forum_threads/show.html.erb +++ b/app/views/simple_discussion/forum_threads/show.html.erb @@ -2,18 +2,32 @@

<%= icon "fas", "thumb-tack", class: "text-muted" if @forum_thread.pinned? %> <%= @forum_thread.title %>

- <% if is_moderator_or_owner?(@forum_thread) %> - <%= link_to icon("fas","trash"), simple_discussion.forum_thread_path(@forum_thread), - class: "text-muted", - method: :delete, - data: { toggle: "tooltip", placement: "left", confirm: "Are you sure you want to delete this thread?" }, - title: t('delete_this_thread') + <% if @forum_thread.solved? %> + <%= link_to t('jump_to_solution'), simple_discussion.forum_thread_path(@forum_thread, anchor: "solution"), + class: "btn btn-outline-success h-25 mr-2", + title: t('jump_to_solution') %> <% end %> + <% if is_moderator_or_owner?(@forum_thread) %> + + <% end %>
+
<%= t("created_by") %> <%= @forum_thread.user.name %> • <%= time_ago_in_words(@forum_thread.created_at) %> <%= t("ago") %> @@ -25,5 +39,6 @@ <%= render partial: "simple_discussion/forum_posts/forum_post", collection: @forum_thread.forum_posts.includes(:user).sorted %> <%= render partial: "simple_discussion/forum_posts/form" if user_signed_in? %> +<%= render partial: "simple_discussion/forum_posts/report_spam_modal_form", locals: { forum_thread: @forum_thread } if user_signed_in? %> <%= render partial: "login_bar" if !user_signed_in? %> diff --git a/app/views/simple_discussion/forum_threads/spam_reports.html.erb b/app/views/simple_discussion/forum_threads/spam_reports.html.erb new file mode 100644 index 0000000..e5ffbdd --- /dev/null +++ b/app/views/simple_discussion/forum_threads/spam_reports.html.erb @@ -0,0 +1,16 @@ + + + + + + + + + + + + <%= render partial: "simple_discussion/forum_posts/spam_report", collection: @spam_reports%> + +
Forum PostReasonReported byView in ThreadDelete
+ + <%= will_paginate @spam_reports, url_builder: simple_discussion, renderer: SimpleDiscussion::BootstrapLinkRenderer %> diff --git a/config/locales/en.yml b/config/locales/en.yml index de519dd..a6943e4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -57,3 +57,6 @@ en: login: "Log in" commented: "commented:" inappropriate_language_error_message: "contains inappropriate language: %{words}" + unmark_as_solution: "Unmark as Solution" + mark_as_solution: "Mark as Solution" + report_post: "Report Post" diff --git a/config/locales/es.yml b/config/locales/es.yml index 851831c..804b5e7 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -56,3 +56,6 @@ es: login: "Iniciar sesión" commented: "comentó:" inappropriate_language_error_message: "contiene lenguaje inapropiado: %{words}" + unmark_as_solution: "Desmarcar como solución" + mark_as_solution: "Marcar como solución" + report_post: "Reportar publicación" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index c137054..31b362c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -58,3 +58,6 @@ fr: login: "Se connecter" commented: "a commenté:" inappropriate_language_error_message: "contient un langage inapproprié : %{words}" + unmark_as_solution: "Démarquer comme solution" + mark_as_solution: "Marquer comme solution" + report_post: "Signaler le post" diff --git a/config/routes.rb b/config/routes.rb index b490da3..5838bfe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,6 +6,8 @@ get :unanswered get :mine get :participating + get :spam_reports + get :leaderboard get "category/:id", to: "forum_categories#index", as: :forum_category end @@ -13,6 +15,7 @@ member do put :solved put :unsolved + post :report_spam end end diff --git a/db/migrate/20240624124709_create_spam_reports.rb b/db/migrate/20240624124709_create_spam_reports.rb new file mode 100644 index 0000000..a744dfc --- /dev/null +++ b/db/migrate/20240624124709_create_spam_reports.rb @@ -0,0 +1,12 @@ +class CreateSpamReports < ActiveRecord::Migration[7.0] + def change + create_table :spam_reports do |t| + t.references :forum_post, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true # The user who reported the post + t.integer :reason, null: false # Enum for reason + t.text :details # optional column if the reason is 'other' + + t.timestamps + end + end +end diff --git a/db/migrate/20240808180738_create_forum_leaderboard.rb b/db/migrate/20240808180738_create_forum_leaderboard.rb new file mode 100644 index 0000000..b18c438 --- /dev/null +++ b/db/migrate/20240808180738_create_forum_leaderboard.rb @@ -0,0 +1,12 @@ +class CreateForumLeaderboard < ActiveRecord::Migration[7.0] + def change + create_table :forum_leaderboards do |t| + t.references :user, null: false, foreign_key: true, index: {unique: true} + t.integer :points, null: false, default: 0 + + t.timestamps + end + + add_index :forum_leaderboards, :points + end +end diff --git a/gemfiles/rails_6_1.gemfile b/gemfiles/rails_6_1.gemfile index 4045170..3d03e3e 100644 --- a/gemfiles/rails_6_1.gemfile +++ b/gemfiles/rails_6_1.gemfile @@ -4,11 +4,11 @@ source "https://rubygems.org" gem "devise" gem "puma" -gem "sprockets-rails" -gem "sqlite3", "~> 1.7.2" gem "appraisal" gem "standardrb" gem "font-awesome-sass", "~> 5.13.1" +gem "sqlite3", "~> 1.7.2" +gem "sprockets-rails" gem "rails", "~> 6.1.0" gemspec path: "../" diff --git a/gemfiles/rails_6_1.gemfile.lock b/gemfiles/rails_6_1.gemfile.lock index cffead0..2f16a98 100644 --- a/gemfiles/rails_6_1.gemfile.lock +++ b/gemfiles/rails_6_1.gemfile.lock @@ -6,6 +6,7 @@ PATH friendly_id (>= 5.2.0) language_filter (>= 0.3.01) rails (>= 4.2) + redcarpet (>= 3.3.4) will_paginate (>= 3.1.0) GEM @@ -122,7 +123,7 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.3) - nokogiri (1.16.6-x86_64-linux) + nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.25.1) @@ -165,6 +166,7 @@ GEM thor (~> 1.0) rainbow (3.1.1) rake (13.2.1) + redcarpet (3.6.0) regexp_parser (2.9.2) responders (3.1.1) actionpack (>= 5.2) diff --git a/gemfiles/rails_7.gemfile b/gemfiles/rails_7.gemfile index 0ddc27e..3294262 100644 --- a/gemfiles/rails_7.gemfile +++ b/gemfiles/rails_7.gemfile @@ -4,11 +4,11 @@ source "https://rubygems.org" gem "devise" gem "puma" -gem "sprockets-rails" -gem "sqlite3", "~> 1.7.2" gem "appraisal" gem "standardrb" gem "font-awesome-sass", "~> 5.13.1" +gem "sqlite3", "~> 1.7.2" +gem "sprockets-rails" gem "rails", "~> 7.0.0" gemspec path: "../" diff --git a/gemfiles/rails_7.gemfile.lock b/gemfiles/rails_7.gemfile.lock index 2ded953..427ffc2 100644 --- a/gemfiles/rails_7.gemfile.lock +++ b/gemfiles/rails_7.gemfile.lock @@ -6,6 +6,7 @@ PATH friendly_id (>= 5.2.0) language_filter (>= 0.3.01) rails (>= 4.2) + redcarpet (>= 3.3.4) will_paginate (>= 3.1.0) GEM @@ -128,7 +129,7 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.3) - nokogiri (1.16.6-x86_64-linux) + nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.25.1) @@ -171,6 +172,7 @@ GEM zeitwerk (~> 2.5) rainbow (3.1.1) rake (13.2.1) + redcarpet (3.6.0) regexp_parser (2.9.2) responders (3.1.1) actionpack (>= 5.2) diff --git a/gemfiles/rails_7_1.gemfile b/gemfiles/rails_7_1.gemfile index c4c702b..fd50374 100644 --- a/gemfiles/rails_7_1.gemfile +++ b/gemfiles/rails_7_1.gemfile @@ -4,11 +4,11 @@ source "https://rubygems.org" gem "devise" gem "puma" -gem "sprockets-rails" -gem "sqlite3", "~> 1.7.2" gem "appraisal" gem "standardrb" gem "font-awesome-sass", "~> 5.13.1" +gem "sqlite3", "~> 1.7.2" +gem "sprockets-rails" gem "rails", "~> 7.1.0" gemspec path: "../" diff --git a/gemfiles/rails_7_1.gemfile.lock b/gemfiles/rails_7_1.gemfile.lock index d2ea195..b603725 100644 --- a/gemfiles/rails_7_1.gemfile.lock +++ b/gemfiles/rails_7_1.gemfile.lock @@ -6,6 +6,7 @@ PATH friendly_id (>= 5.2.0) language_filter (>= 0.3.01) rails (>= 4.2) + redcarpet (>= 3.3.4) will_paginate (>= 3.1.0) GEM @@ -145,7 +146,7 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.3) - nokogiri (1.16.6-x86_64-linux) + nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.25.1) @@ -198,6 +199,7 @@ GEM rake (13.2.1) rdoc (6.7.0) psych (>= 4.0.0) + redcarpet (3.6.0) regexp_parser (2.9.2) reline (0.5.9) io-console (~> 0.5) diff --git a/gemfiles/rails_main.gemfile b/gemfiles/rails_main.gemfile index 1cdd81c..f943de1 100644 --- a/gemfiles/rails_main.gemfile +++ b/gemfiles/rails_main.gemfile @@ -4,11 +4,11 @@ source "https://rubygems.org" gem "devise" gem "puma" -gem "sprockets-rails" -gem "sqlite3", "~> 2.0" gem "appraisal" gem "standardrb" gem "font-awesome-sass", "~> 5.13.1" +gem "sqlite3", "~> 2.0" +gem "sprockets-rails" gem "rails", branch: "main", git: "https://github.com/rails/rails" gemspec path: "../" diff --git a/gemfiles/rails_main.gemfile.lock b/gemfiles/rails_main.gemfile.lock index 25885b2..c138c8d 100644 --- a/gemfiles/rails_main.gemfile.lock +++ b/gemfiles/rails_main.gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/rails/rails - revision: 0db535f88d856bcac66faee901df51aa493b639b + revision: c01ee683aa700b109f704a5b6611107fe2dc5476 branch: main specs: actioncable (8.0.0.alpha) @@ -103,6 +103,7 @@ PATH friendly_id (>= 5.2.0) language_filter (>= 0.3.01) rails (>= 4.2) + redcarpet (>= 3.3.4) will_paginate (>= 3.1.0) GEM @@ -177,17 +178,17 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.3) - nokogiri (1.16.6-aarch64-linux) + nokogiri (1.16.7-aarch64-linux) racc (~> 1.4) - nokogiri (1.16.6-arm-linux) + nokogiri (1.16.7-arm-linux) racc (~> 1.4) - nokogiri (1.16.6-arm64-darwin) + nokogiri (1.16.7-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.6-x86-linux) + nokogiri (1.16.7-x86-linux) racc (~> 1.4) - nokogiri (1.16.6-x86_64-darwin) + nokogiri (1.16.7-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.6-x86_64-linux) + nokogiri (1.16.7-x86_64-linux) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.25.1) @@ -218,6 +219,7 @@ GEM rake (13.2.1) rdoc (6.7.0) psych (>= 4.0.0) + redcarpet (3.6.0) regexp_parser (2.9.2) reline (0.5.9) io-console (~> 0.5) diff --git a/lib/simple_discussion.rb b/lib/simple_discussion.rb index 974718d..57ed634 100644 --- a/lib/simple_discussion.rb +++ b/lib/simple_discussion.rb @@ -13,9 +13,16 @@ module SimpleDiscussion mattr_accessor :send_email_notifications mattr_accessor :send_slack_notifications mattr_accessor :profanity_filter + mattr_accessor :markdown_circuit_embed + mattr_accessor :markdown_video_embed + mattr_accessor :markdown_user_tagging + @@send_email_notifications = true @@send_slack_notifications = true @@profanity_filter = true + @@markdown_circuit_embed = true + @@markdown_video_embed = true + @@markdown_user_tagging = true def self.setup yield self diff --git a/lib/simple_discussion/engine.rb b/lib/simple_discussion/engine.rb index 5181d2c..e32ac37 100644 --- a/lib/simple_discussion/engine.rb +++ b/lib/simple_discussion/engine.rb @@ -6,5 +6,24 @@ class Engine < ::Rails::Engine config.after_initialize do SimpleDiscussion::Engine.routes.default_url_options = ActionMailer::Base.default_url_options end + + # javascripts assets precompiled for dropdown and markdown text editor + @@javascripts = [] + + initializer "simple_discussion.assets.precompile" do |app| + app.config.assets.precompile += [ + "simple_discussion/application.js" + ] + end + + def self.add_javascript(script) + @@javascripts << script + end + + def self.javascripts + @@javascripts + end + + add_javascript "simple_discussion/application.js" end end diff --git a/lib/simple_discussion/forum_user.rb b/lib/simple_discussion/forum_user.rb index 9995ca6..6e6245c 100644 --- a/lib/simple_discussion/forum_user.rb +++ b/lib/simple_discussion/forum_user.rb @@ -6,6 +6,7 @@ module ForumUser has_many :forum_threads has_many :forum_posts has_many :forum_subscriptions + has_one :forum_leaderboard end end end diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..341a78c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,118 @@ +{ + "name": "simple_discussion", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "simple_discussion", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@hotwired/stimulus": "^3.2.2", + "bootstrap": "^5.3.3", + "esbuild": "^0.20.2", + "marked": "^13.0.2" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@hotwired/stimulus": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.2.tgz", + "integrity": "sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/marked": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.2.tgz", + "integrity": "sha512-J6CPjP8pS5sgrRqxVRvkCIkZ6MFdRIjDkwUwgJ9nL2fbmM6qGQeB2C16hi8Cc9BOzj6xXzy0jyi0iPIfnMHYzA==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4340c5f --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "simple_discussion", + "version": "1.0.0", + "description": "javascript asset precompiling for this gem", + "author": "CircuitVerse", + "license": "MIT", + "homepage": "https://github.com/circuitverse/simple_discussion#readme", + "scripts": { + "build": "esbuild app/assets/javascripts/simple_discussion/application.js --bundle --sourcemap --outdir=app/assets/builds/simple_discussion --public-path=/assets/simple_discussion" + }, + "dependencies": { + "@hotwired/stimulus": "^3.2.2", + "bootstrap": "^5.3.3", + "esbuild": "^0.20.2", + "marked": "^13.0.2" + } +} diff --git a/simple_discussion.gemspec b/simple_discussion.gemspec index d7da08a..824e7db 100644 --- a/simple_discussion.gemspec +++ b/simple_discussion.gemspec @@ -27,5 +27,7 @@ Gem::Specification.new do |spec| spec.add_dependency "rails", ">= 4.2" spec.add_dependency "will_paginate", ">= 3.1.0" spec.add_dependency "language_filter", ">= 0.3.01" + spec.add_dependency "redcarpet", ">= 3.3.4" + spec.metadata["rubygems_mfa_required"] = "true" end diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb index de6be79..fb1a9b2 100644 --- a/test/dummy/app/helpers/application_helper.rb +++ b/test/dummy/app/helpers/application_helper.rb @@ -1,2 +1,5 @@ module ApplicationHelper + def user_profile_link(user) + "/users/#{user.id}" + end end diff --git a/test/dummy/app/models/user.rb b/test/dummy/app/models/user.rb index 5286d1f..a420c59 100644 --- a/test/dummy/app/models/user.rb +++ b/test/dummy/app/models/user.rb @@ -5,4 +5,8 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable + + def moderator? + moderator + end end diff --git a/test/dummy/db/migrate/20240813072347_add_moderator_to_users.rb b/test/dummy/db/migrate/20240813072347_add_moderator_to_users.rb new file mode 100644 index 0000000..a3cfacd --- /dev/null +++ b/test/dummy/db/migrate/20240813072347_add_moderator_to_users.rb @@ -0,0 +1,5 @@ +class AddModeratorToUsers < ActiveRecord::Migration[7.0] + def change + add_column :users, :moderator, :boolean, default: false + end +end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index 6eba6dd..bdae3a6 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/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: 2021_03_31_160746) do +ActiveRecord::Schema.define(version: 2024_08_13_072347) do create_table "forum_categories", force: :cascade do |t| t.string "name", null: false t.string "slug", null: false @@ -19,6 +19,15 @@ t.datetime "updated_at" end + create_table "forum_leaderboards", force: :cascade do |t| + t.integer "user_id", null: false + t.integer "points", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["points"], name: "index_forum_leaderboards_on_points" + t.index ["user_id"], name: "index_forum_leaderboards_on_user_id", unique: true + end + create_table "forum_posts", force: :cascade do |t| t.integer "forum_thread_id" t.integer "user_id" @@ -48,23 +57,38 @@ t.datetime "updated_at" end + create_table "spam_reports", force: :cascade do |t| + t.integer "forum_post_id", null: false + t.integer "user_id", null: false + t.integer "reason", null: false + t.text "details" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["forum_post_id"], name: "index_spam_reports_on_forum_post_id" + t.index ["user_id"], name: "index_spam_reports_on_user_id" + end + create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "name" + t.boolean "moderator", default: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end + add_foreign_key "forum_leaderboards", "users" add_foreign_key "forum_posts", "forum_threads" add_foreign_key "forum_posts", "users" add_foreign_key "forum_subscriptions", "forum_threads" add_foreign_key "forum_subscriptions", "users" add_foreign_key "forum_threads", "forum_categories" add_foreign_key "forum_threads", "users" + add_foreign_key "spam_reports", "forum_posts" + add_foreign_key "spam_reports", "users" end diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 297acdf..4323c34 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -5,3 +5,8 @@ one: two: name: "Second User" email: "second@example.com" + +moderator: + name: "Moderator" + email: "moderator@moderator.com" + moderator: true diff --git a/test/integration/forum_test.rb b/test/integration/forum_test.rb index 882dabf..f71437b 100644 --- a/test/integration/forum_test.rb +++ b/test/integration/forum_test.rb @@ -6,27 +6,34 @@ class ForumTest < ActionDispatch::IntegrationTest include SimpleDiscussion::Engine.routes.url_helpers setup do - sign_in users(:one) + @regular_user = users(:one) + @moderator_user = users(:moderator) + @forum_thread = forum_threads(:hello) + @forum_post = forum_posts(:hello) @filter = LanguageFilter::Filter.new end test "threads index" do + sign_in @regular_user get "/" assert_response :success assert_match "Community", response.body end test "categories" do + sign_in @regular_user get forum_category_forum_threads_path(forum_categories(:general)) assert_response :success end test "show forum thread" do - get forum_thread_path(forum_threads(:hello)) + sign_in @regular_user + get forum_thread_path(@forum_thread) assert_response :success end test "create a forum thread" do + sign_in @regular_user assert_difference "ForumThread.count" do assert_difference "ForumPost.count" do post forum_threads_path, params: { @@ -45,8 +52,9 @@ class ForumTest < ActionDispatch::IntegrationTest end test "reply to a forum thread" do + sign_in @regular_user assert_difference "ForumPost.count" do - post forum_thread_forum_posts_path(forum_threads(:hello)), params: { + post forum_thread_forum_posts_path(@forum_thread), params: { forum_post: { body: "Reply" } @@ -57,6 +65,7 @@ class ForumTest < ActionDispatch::IntegrationTest end test "cannot create a forum thread with inappropriate language in title" do + sign_in @regular_user inappropriate_word = @filter.matchlist.to_a.sample assert_no_difference "ForumThread.count" do assert_no_difference "ForumPost.count" do @@ -77,6 +86,8 @@ class ForumTest < ActionDispatch::IntegrationTest end test "cannot create a forum thread with inappropriate language in body" do + sign_in @regular_user + inappropriate_word = @filter.matchlist.to_a.sample assert_no_difference "ForumThread.count" do assert_no_difference "ForumPost.count" do @@ -97,9 +108,11 @@ class ForumTest < ActionDispatch::IntegrationTest end test "cannot reply to a forum thread with inappropriate language" do + sign_in @regular_user + inappropriate_word = @filter.matchlist.to_a.sample assert_no_difference "ForumPost.count" do - post forum_thread_forum_posts_path(forum_threads(:hello)), params: { + post forum_thread_forum_posts_path(@forum_thread), params: { forum_post: { body: "contains inappropriate language: #{inappropriate_word}" } @@ -111,6 +124,8 @@ class ForumTest < ActionDispatch::IntegrationTest end test "can create a forum thread with appropriate language in title and body" do + sign_in @regular_user + assert_difference "ForumThread.count" do assert_difference "ForumPost.count" do post forum_threads_path, params: { @@ -127,4 +142,127 @@ class ForumTest < ActionDispatch::IntegrationTest assert_redirected_to forum_thread_path(ForumThread.last) end + + test "can report a post" do + sign_in @regular_user + + assert_difference "SpamReport.count" do + post report_spam_forum_thread_forum_post_path(@forum_thread, @forum_post), params: { + reason: "irrelevant_content" + } + end + assert_redirected_to forum_thread_path(@forum_thread, anchor: dom_id(@forum_post)) + + spam_report = SpamReport.last + assert_equal @forum_post, spam_report.forum_post + assert_equal @regular_user, spam_report.user + assert_equal "irrelevant_content", spam_report.reason + end + + test "can report a post with 'other' reason and details" do + sign_in @regular_user + + assert_difference "SpamReport.count" do + post report_spam_forum_thread_forum_post_path(@forum_thread, @forum_post), params: { + reason: "others", + details: "This post contains copyrighted material." + } + end + + assert_redirected_to forum_thread_path(@forum_thread, anchor: dom_id(@forum_post)) + + spam_report = SpamReport.last + assert_equal "others", spam_report.reason + assert_equal "This post contains copyrighted material.", spam_report.details + end + + test "modeartor can view spam reports page" do + sign_in @moderator_user + + get spam_reports_forum_threads_path + assert_response :success + end + + test "regular user can't view spam reports page" do + sign_in @regular_user + + get spam_reports_forum_threads_path + assert_response :redirect + assert_redirected_to root_path + end + + test "leaderboard page" do + sign_in @regular_user + get leaderboard_forum_threads_path + assert_response :success + assert_match "Leaderboard", response.body + end + + test "distribute leaderboard points on new forum thread" do + sign_in @regular_user + + initial_leaderboard = @regular_user.forum_leaderboard || @regular_user.create_forum_leaderboard(points: 0) + initial_points = initial_leaderboard.points + + post forum_threads_path, params: { + forum_thread: { + forum_category_id: forum_categories(:general).id, + title: "Test Thread", + forum_posts_attributes: [{ + body: "Hello test thread" + }] + } + } + assert_response :redirect + follow_redirect! + assert_response :success + + @regular_user.reload + assert_equal initial_points + SimpleDiscussion::ForumThreadsController::POINTS[:create_thread], @regular_user.forum_leaderboard.points + end + + test "delete leaderboard points on deleting forum thread by author of thread" do + sign_in @regular_user + + initial_leaderboard = @regular_user.forum_leaderboard || @regular_user.create_forum_leaderboard(points: 0) + initial_points = initial_leaderboard.points + + thread = @regular_user.forum_threads.last + + assert_difference -> { @regular_user.forum_leaderboard.reload.points }, SimpleDiscussion::ForumThreadsController::POINTS[:delete_thread] do + delete forum_thread_path(thread) + end + + assert_response :redirect + follow_redirect! + assert_response :success + + @regular_user.reload + assert_equal initial_points + SimpleDiscussion::ForumThreadsController::POINTS[:delete_thread], @regular_user.forum_leaderboard.points + end + + test "delete leaderboard points on deleting forum thread by moderator" do + sign_in @moderator_user + + thread = ForumThread.create!( + user: @regular_user, + forum_category_id: forum_categories(:general).id, + title: "Thread to be deleted by moderator", + forum_posts_attributes: [{body: "This will be deleted by moderator", user: @regular_user}] + ) + + initial_leaderboard = @regular_user.forum_leaderboard || @regular_user.create_forum_leaderboard(points: 0) + initial_points = initial_leaderboard.points + + assert_difference -> { @regular_user.forum_leaderboard.reload.points }, SimpleDiscussion::ForumThreadsController::POINTS[:delete_reported_thread_by_moderator] do + delete forum_thread_path(thread) + end + + assert_response :redirect + follow_redirect! + assert_response :success + + @regular_user.reload + assert_equal initial_points + SimpleDiscussion::ForumThreadsController::POINTS[:delete_reported_thread_by_moderator], @regular_user.forum_leaderboard.points + end end