diff --git a/app/assets/javascripts/mod_warning.js b/app/assets/javascripts/mod_warning.js new file mode 100644 index 0000000000000000000000000000000000000000..8e0fb8f87d376dac018463dc0db9faae5014775b --- /dev/null +++ b/app/assets/javascripts/mod_warning.js @@ -0,0 +1,6 @@ +$(function () { + $(".js--warning-template-selection").on("input", (e) => { + const $this = $(e.target); + $(".js--warning-template-target textarea").val(atob($this.val())); + }); +}) \ No newline at end of file diff --git a/app/assets/stylesheets/mod_warning.scss b/app/assets/stylesheets/mod_warning.scss new file mode 100644 index 0000000000000000000000000000000000000000..d77d915eff6525e69d02a0413d65aa5b40b61b04 --- /dev/null +++ b/app/assets/stylesheets/mod_warning.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the ModWarning controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9433e846c205b865143dcb11c79be5211580af9b..da7b96cdd67a1b2f57a7dd3f5cd6f1f03e335032 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller? before_action :set_globals + before_action :check_if_warning_or_suspension_pending def upload redirect_to helpers.upload_remote_url(params[:key]) @@ -132,4 +133,18 @@ class ApplicationController < ActionController::Base Category.all.order(sequence: :asc, id: :asc) end end + + def check_if_warning_or_suspension_pending + return if current_user.nil? + + warning = ModWarning.where(community_user: current_user.community_user, active: true).any? + return unless warning + + # Ignore devise and warning routes + return if devise_controller? || ['custom_sessions', 'mod_warning', 'errors'].include?(controller_name) + + flash.clear + + redirect_to(current_mod_warning_path) + end end diff --git a/app/controllers/mod_warning_controller.rb b/app/controllers/mod_warning_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..bbdc1f5b2ce4ce57927e880648085c805ae4ecae --- /dev/null +++ b/app/controllers/mod_warning_controller.rb @@ -0,0 +1,73 @@ +class ModWarningController < ApplicationController + before_action :verify_moderator, only: [:log, :new, :create] + + before_action :set_warning, only: [:current, :approve] + before_action :set_user, only: [:log, :new, :create] + + def current + render layout: 'without_sidebar' + end + + def approve + return not_found if @warning.suspension_active? + + if params[:approve_checkbox].nil? + @failed_to_click_checkbox = true + return render 'current', layout: 'without_sidebar' + end + + @warning.update(active: false) + redirect_to(root_path) + end + + def log + @warnings = ModWarning.where(community_user: @user.community_user).order(created_at: :desc).all + render layout: 'without_sidebar' + end + + def new + @templates = WarningTemplate.where(active: true).all + @prior_warning_count = ModWarning.where(community_user: @user.community_user).order(created_at: :desc).count + @warning = ModWarning.new(author: current_user, community_user: @user.community_user) + render layout: 'without_sidebar' + end + + def create + suspension_duration = params[:mod_warning][:suspension_duration].to_i + + suspension_duration = 1 if suspension_duration <= 0 + suspension_duration = 365 if suspension_duration > 365 + + suspension_end = DateTime.now + suspension_duration.days + + @warning = ModWarning.new(author: current_user, community_user: @user.community_user, + body: params[:mod_warning][:body], is_suspension: params[:mod_warning][:is_suspension], + suspension_end: suspension_end, active: true) + if @warning.save + if params[:mod_warning][:is_suspension] + @user.community_user.update(is_suspended: true, suspension_end: suspension_end, + suspension_public_comment: params[:mod_warning][:suspension_public_notice]) + end + + redirect_to user_path(@user) + else + render :new + end + end + + private + + def set_warning + @warning = ModWarning.where(community_user: current_user.community_user, active: true).last + not_found if @warning.nil? + end + + def set_user + @user = user_scope.find_by(id: params[:user_id]) + not_found if @user.nil? + end + + def user_scope + User.joins(:community_user).includes(:community_user, :avatar_attachment) + end +end diff --git a/app/helpers/mod_warning_helper.rb b/app/helpers/mod_warning_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..56c7a0275ccdebecc7b4177c0ca76b358f4168e2 --- /dev/null +++ b/app/helpers/mod_warning_helper.rb @@ -0,0 +1,2 @@ +module ModWarningHelper +end diff --git a/app/models/community_user.rb b/app/models/community_user.rb index a897b8eb85090fca9a54c241e3e3121cdfb0c722..4917e529e2fe158332bfd487b9526c84f7967fc4 100644 --- a/app/models/community_user.rb +++ b/app/models/community_user.rb @@ -2,7 +2,19 @@ class CommunityUser < ApplicationRecord belongs_to :community belongs_to :user + has_many :mod_warnings, dependent: :destroy + validates :user_id, uniqueness: { scope: [:community_id] } scope :for_context, -> { where(community_id: RequestContext.community_id) } + + def suspended? + return true if is_suspended && !suspension_end.past? + + if is_suspended + update(is_suspended: false, suspension_public_comment: nil, suspension_end: nil) + end + + false + end end diff --git a/app/models/mod_warning.rb b/app/models/mod_warning.rb new file mode 100644 index 0000000000000000000000000000000000000000..f28f98b1a990871f916461bcb68c39ed6bdba33f --- /dev/null +++ b/app/models/mod_warning.rb @@ -0,0 +1,24 @@ +class ModWarning < ApplicationRecord + # Warning class name not accepted by Rails, hence this needed + self.table_name = 'warnings' + + belongs_to :community_user + belongs_to :author, class_name: 'User' + + def suspension_active? + active && is_suspension && !suspension_end.past? + end + + def body_as_html + ApplicationController.helpers.render_markdown(body) + end + + # These two are neccessary for the new warning form to work. + def suspension_duration + 1 + end + + def suspension_public_notice + nil + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 47dcfbd6bc3e33c69e634aca8075b7e529bf7b6e..93cfa9d88cc02c648b9e444d56ce6bcabab3e2d6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,6 +19,8 @@ class User < ApplicationRecord has_many :suggested_edits, dependent: :destroy has_many :suggested_edits_decided, class_name: 'SuggestedEdit', foreign_key: 'decided_by_id', dependent: :nullify + has_many :mod_warning_author, class_name: 'ModWarning', foreign_key: 'author_id', dependent: :nullify + validates :username, presence: true, length: { minimum: 3, maximum: 50 } validates :login_token, uniqueness: { allow_nil: true, allow_blank: true } validate :no_links_in_username diff --git a/app/models/warning_template.rb b/app/models/warning_template.rb new file mode 100644 index 0000000000000000000000000000000000000000..08c4d3a3694b1d983e1923f8805d9e698ce326a9 --- /dev/null +++ b/app/models/warning_template.rb @@ -0,0 +1,18 @@ +require 'base64' + +class WarningTemplate < ApplicationRecord + include CommunityRelated + + def body_as_b64 + body_with_site_replacements = body.gsub '$SiteName', SiteSetting['SiteName'] + + chat_link = if SiteSetting['ChatLink'].nil? + 'chat' + else + '[chat](' + SiteSetting['ChatLink'] + ')' + end + body_with_site_replacements = body_with_site_replacements.gsub '$ChatLink', chat_link + + Base64.encode64(body_with_site_replacements) + end +end diff --git a/app/views/mod_warning/current.html.erb b/app/views/mod_warning/current.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..4ed24aec12122c750543c2acac56d8ab0e4b181e --- /dev/null +++ b/app/views/mod_warning/current.html.erb @@ -0,0 +1,51 @@ +<h1>Important message</h1> + +<p class="is-lead">Hello <%= current_user.username %>, you have an important message from the <%= SiteSetting['SiteName'] %> community moderation team:</p> + + +<% if @warning.is_suspension %> + <% if @warning.suspension_active? %> + <div class="notice is-danger"> + <%= raw(sanitize(@warning.body_as_html, scrubber: scrubber)) %> + <p>Your account has been <strong>temporarily suspended</strong> (ends <span title="<%= current_user.community_user.suspension_end.iso8601 %>">in <%= time_ago_in_words(current_user.community_user.suspension_end) %></span>). We look forward to your return and continued contributions to the site after this period. In the event of continued rule violations after this period, your account may be suspended for longer periods. If you have any questions about this suspension or would like to dispute it, <a href="mailto:info@codidact.org">contact us</a>.</p> + + <%= link_to 'Sign Out', destroy_user_session_path, method: :delete, class: 'button is-danger is-outlined' %> + </div> + <% else %> + <div class="notice is-danger"> + <%= raw(sanitize(@warning.body_as_html, scrubber: scrubber)) %> + <p>Your account was <strong>temporarily suspended</strong>, but the suspension period is now over. We look forward to your return and continued contributions to the site. In the event of continued rule violations after this period, however, your account may be suspended for longer periods. If you have any questions regarding the site rules, you can ask them in the Meta category of this site or on <a href="https://meta.codidact.com/">meta.codidact.com</a>.</p></p> + + <%= form_with url: current_mod_warning_approve_path, method: 'post' do %> + <label for="approve-checkbox" class="form-element h-m-v-2"> + <input type="checkbox" class="form-checkbox-element" id="approve-checkbox" name="approve_checkbox"> + <% if @failed_to_click_checkbox %> + <strong>You need to accept this in order to continue:</strong> + <% end %> + I have read this suspension message and will follow the rules from now on. + </label> + + <%= submit_tag 'Continue', class: 'button is-filled' %> + <%= link_to 'Sign Out', destroy_user_session_path, method: :delete, class: 'button is-danger is-outlined' %> + <% end %> + </div> + <% end %> +<% else %> + <div class="notice is-warning"> + <%= raw(sanitize(@warning.body_as_html, scrubber: scrubber)) %> + <p>This is <strong>a formal warning</strong> from the moderation team. In the event of continued violations of the site rules, your account may be suspended. If you have any questions regarding the site rules, you can ask them in the Meta category of this site or on <a href="https://meta.codidact.com/">meta.codidact.com</a>. If you have any questions about this warning or would like to dispute it, <a href="mailto:info@codidact.org">contact us</a>.</p> + + <%= form_with url: current_mod_warning_approve_path, method: 'post' do %> + <label for="approve-checkbox" class="form-element h-m-v-2"> + <input type="checkbox" class="form-checkbox-element" id="approve-checkbox" name="approve_checkbox"> + <% if @failed_to_click_checkbox %> + <strong>You need to accept this in order to continue:</strong> + <% end %> + I have read this warning and will follow the rules from now on. + </label> + + <%= submit_tag 'Continue', class: 'button is-filled' %> + <%= link_to 'Sign Out', destroy_user_session_path, method: :delete, class: 'button is-danger is-outlined' %> + <% end %> + </div> +<% end %> \ No newline at end of file diff --git a/app/views/mod_warning/log.html.erb b/app/views/mod_warning/log.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..e340e55ca59a389f7290b069804f56d1159c9088 --- /dev/null +++ b/app/views/mod_warning/log.html.erb @@ -0,0 +1,41 @@ +<h1>Warnings send to <%= link_to @user.username, user_path(@user.id) %></h1> + +<table class="table is-full-width is-with-hover"> + <tr> + <th>Date</th> + <th>Type</th> + <th>From</th> + <th>Excerpt</th> + <th>Status</th> + </tr> + <% @warnings.each do |w| %> + <tr> + <td> + <span title="<%= w.created_at.iso8601 %>"><%= time_ago_in_words(w.created_at) %> ago</span> + </td> + <td> + <% if w.is_suspension %> + <% diff = ((w.suspension_end - w.created_at) / (3600 * 24)).to_i %> + <span class="badge is-tag is-red is-filled">Suspension</span> (<%= diff %>d) + <% else %> + <span class="badge is-tag is-muted">Warning</span> + <% end %> + </td> + <td> + <%= link_to w.author.username, user_path(w.author.id) %> + </td> + <td> + <%= raw(sanitize(render_markdown(w.body), scrubber: scrubber)) %> + </td> + <td> + <% if w.suspension_active? %> + <strong>Current</strong> + <% elsif w.active %> + <strong>Unread</strong> + <% else %> + Read + <% end %> + </td> + </tr> + <% end %> +</table> \ No newline at end of file diff --git a/app/views/mod_warning/new.html.erb b/app/views/mod_warning/new.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..5e3d5ed5fe160d70de212256cb491ea1ca5de049 --- /dev/null +++ b/app/views/mod_warning/new.html.erb @@ -0,0 +1,79 @@ +<% content_for :head do %> + <%= render 'posts/markdown_script' %> +<% end %> + +<h1>Warn or suspend <%= link_to @user.username, user_path(@user.id) %></h1> + +<div class="notice is-warning"> + <p>Use the warning tool only against users who have violated the site rules. Prefer other measurements, such as friendly asking the user to stop certain behaviors in a comment.</p> +</div> + +<%= form_for @warning, url: create_mod_warning_path(@user.id), method: :post do |f| %> +<div class="widget"> + <div class="widget--header h-bg-tertiary-050"> + 1. Choose a template + </div> + <div class="widget--body"> + <div class="h-p-4"> + <p class="is-lead">Choose a template, which explains, why you are contacting the user. If none is applicable, choose to send a custom message.</p> + <label class="form-element" for="template">Template</label> + <select id="template" class="form-element js--warning-template-selection"> + <option value="">Send custom message</option> + <% @templates.each do |t| %> + <option value="<%= t.body_as_b64 %>"><%= t.name %></option> + <% end %> + </select> + </div> + </div> + <div class="widget--header h-bg-tertiary-050 has-border-top-width-1 has-border-top-style-solid has-border-color-tertiary-100"> + 2. Review the message + </div> + <div class="widget--body"> + <div class="h-p-4"> + <p class="is-lead">Review the generated message and add details. Do not add salutations or informations about possible suspensions, as they are generated automatically.</p> + + <div class="js--warning-template-target"> + <%= render 'shared/body_field', f: f, field_name: :body, field_label: 'Body' %> + <div class="post-preview"></div> + </div> + </div> + </div> + <div class="widget--header h-bg-tertiary-050 has-border-top-width-1 has-border-top-style-solid has-border-color-tertiary-100"> + 3. Choose optional suspension + </div> + <div class="widget--body"> + <div class="h-p-4"> + <p class="is-lead">Decide, whether or not to suspend the user, and if, for how long. Choose an optional message shown publicly on the user profile.</p> + + <% if @prior_warning_count == 0 %> + <p><strong>Info:</strong> This user has <a href="<%= mod_warning_log_path(@user.id) %>"><strong>no prior warnings</strong></a>. The system recommends issuing only a warning, unless the user is destructive and needs to be stopped immediately.</p> + <% elsif @prior_warning_count >= 5 %> + <p><strong>Info:</strong> This user has <a href="<%= mod_warning_log_path(@user.id) %>"><strong><%= @prior_warning_count %> prior warnings</strong></a>. The system recommends suspending them for 365 days (the maximum).</p> + <% else %> + <% lengths = { 1 => 3, 2 => 7, 3 => 30, 4 => 180 } %> + <p><strong>Info:</strong> This user has <a href="<%= mod_warning_log_path(@user.id) %>"><strong><%= @prior_warning_count %> prior warnings</strong></a>. The system recommends suspending them for <%= lengths[@prior_warning_count] %> days.</p> + <% end %> + + <div class="h-p-2"> + <%= f.label :is_suspension, 'Suspend this user account?', class: 'form-element' %> + <label class="form-element"><%= f.radio_button :is_suspension, true %> Yes</label> + <label class="form-element"><%= f.radio_button :is_suspension, false %> No</label> + </div> + + <div class="h-p-2"> + <%= f.label :suspension_duration, 'If suspending, for how long?', class: 'form-element' %> + <div class="form-caption">Enter the number of days. At least 1, at most 365.</div> + <%= f.number_field :suspension_duration, in: 1..365, class: 'form-element' %> + </div> + + <div class="h-p-2"> + <%= f.label :suspension_public_notice, 'If suspending, what public notice, if any, do you want to show?', class: 'form-element' %> + <%= f.select :suspension_public_notice, options_for_select([['for rule violations', 'for rule violations'], ['to cool down', 'to cool down']]), { include_blank: true }, class: 'form-element' %> + </div> + </div> + </div> + <div class="widget--footer"> + <%= f.submit 'Warn (or suspend) user', class: 'button is-danger is-filled' %> + </div> +</div> +<% end %> \ No newline at end of file diff --git a/app/views/users/mod.html.erb b/app/views/users/mod.html.erb index 4eb7d4f5a696cbcc7672db5eb77c4bf7b5686f6c..f8760bd40b7a11402b9801f6e7db76378f48ea5c 100644 --- a/app/views/users/mod.html.erb +++ b/app/views/users/mod.html.erb @@ -7,6 +7,8 @@ <div class="widget--body"> <ul> <li><a href="/mod/votes/user/<%= @user.id %>">suspicious voting patterns</a></li> + <li><a href="/warning/log/<%= @user.id %>">warnings and suspensions sent to user</a></li> + <li><a href="/warning/new/<%= @user.id %>">warn or suspend user</a></li> </ul> </div> </div> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 8fa7632a1e07aaedf8943075445574c0317ea7cd..59f651d6f288909b10ff6ea89fa676e5455f26b3 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,5 +1,17 @@ <% content_for :title, "User #{@user.username}" %> +<% if @user.community_user.suspended? %> +<div class="notice is-danger"> +<% if @user.community_user.suspension_public_comment.nil? %> + <p>This user has been <strong>temporarily suspended</strong>. +<% else %> + <p>This user has been <strong>temporarily suspended</strong> <%= @user.community_user.suspension_public_comment %>. +<% end %> + + The suspension ends <span title="<%= @user.community_user.suspension_end.iso8601 %>">in <%= time_ago_in_words(@user.community_user.suspension_end) %></span>.</p> +</div> +<% end %> + <div class="tabs"> <% if @user == current_user %> <%= link_to "", class: "tabs--item is-active" do %> diff --git a/config/routes.rb b/config/routes.rb index 0935b631fc660d14c3a8ff766b0594015cfaa7dc..6f84a49bd62dd24e4c105f9fee4f2d37f94e26f5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -183,6 +183,12 @@ Rails.application.routes.draw do get ':id/feed', to: 'categories#rss_feed', as: :category_feed end + get 'warning', to: 'mod_warning#current', as: :current_mod_warning + post 'warning/approve', to: 'mod_warning#approve', as: :current_mod_warning_approve + get 'warning/log/:user_id', to: 'mod_warning#log', as: :mod_warning_log + get 'warning/new/:user_id', to: 'mod_warning#new', as: :new_mod_warning + post 'warning/new/:user_id', to: 'mod_warning#create', as: :create_mod_warning + get 'uploads/:key', to: 'application#upload', as: :uploaded scope 'dashboard' do diff --git a/db/migrate/20200618094826_add_suspensions_and_warnings.rb b/db/migrate/20200618094826_add_suspensions_and_warnings.rb new file mode 100644 index 0000000000000000000000000000000000000000..ec78c7a0eb8dbc2b3457d06c824a46a0d207118a --- /dev/null +++ b/db/migrate/20200618094826_add_suspensions_and_warnings.rb @@ -0,0 +1,20 @@ +class AddSuspensionsAndWarnings < ActiveRecord::Migration[5.2] + def change + create_table :warnings do |t| + t.references :community_user, foreign_key: true + + t.text :body, null: true + t.boolean :is_suspension + t.datetime :suspension_end + t.boolean :active + + t.references :author, foreign_key: {to_table: :users}, null: true + + t.timestamps + end + + add_column :community_users, :is_suspended, :boolean + add_column :community_users, :suspension_end, :datetime + add_column :community_users, :suspension_public_comment, :string + end +end diff --git a/db/migrate/20200618124645_add_warning_templates.rb b/db/migrate/20200618124645_add_warning_templates.rb new file mode 100644 index 0000000000000000000000000000000000000000..d4abcebbfca47589c63d90cc7421ccbf4c5147d9 --- /dev/null +++ b/db/migrate/20200618124645_add_warning_templates.rb @@ -0,0 +1,13 @@ +class AddWarningTemplates < ActiveRecord::Migration[5.2] + def change + create_table :warning_templates do |t| + t.references :community, foreign_key: true + + t.string :name + t.text :body + t.boolean :active + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index dcff2f3f851dbf62a2ecbc27c52f11f261949a1d..78959a5a2a15d4f48ba07b41979767fe775bfeab 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -114,6 +114,9 @@ ActiveRecord::Schema.define(version: 2020_06_25_115618) do t.integer "reputation" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "is_suspended" + t.datetime "suspension_end" + t.string "suspension_public_comment" t.index ["community_id"], name: "index_community_users_on_community_id" t.index ["user_id"], name: "index_community_users_on_user_id" end @@ -222,7 +225,7 @@ ActiveRecord::Schema.define(version: 2020_06_25_115618) do t.integer "post_type_id", null: false t.text "body_markdown" t.integer "answer_count", default: 0, null: false - t.datetime "last_activity", default: -> { "CURRENT_TIMESTAMP" }, null: false + t.datetime "last_activity", default: -> { "current_timestamp()" }, null: false t.string "att_source" t.string "att_license_name" t.string "att_license_link" @@ -416,6 +419,29 @@ ActiveRecord::Schema.define(version: 2020_06_25_115618) do t.index ["user_id"], name: "index_votes_on_user_id" end + create_table "warning_templates", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", force: :cascade do |t| + t.bigint "community_id" + t.string "name" + t.text "body" + t.boolean "active" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["community_id"], name: "index_warning_templates_on_community_id" + end + + create_table "warnings", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", force: :cascade do |t| + t.bigint "community_user_id" + t.text "body" + t.boolean "is_suspension" + t.datetime "suspension_end" + t.boolean "active" + t.bigint "author_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["author_id"], name: "index_warnings_on_author_id" + t.index ["community_user_id"], name: "index_warnings_on_community_user_id" + end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "categories", "licenses" add_foreign_key "categories", "tag_sets" @@ -441,4 +467,7 @@ ActiveRecord::Schema.define(version: 2020_06_25_115618) do add_foreign_key "suggested_edits", "users", column: "decided_by_id" add_foreign_key "tags", "communities" add_foreign_key "votes", "communities" + add_foreign_key "warning_templates", "communities" + add_foreign_key "warnings", "community_users" + add_foreign_key "warnings", "users", column: "author_id" end diff --git a/db/seeds/posts.yml b/db/seeds/posts.yml index 5993502e6e9fb4ee171147074285b8ab46cfc995..61e95e70a032e6ecb373cf58dbdf4319ef2d5f32 100644 --- a/db/seeds/posts.yml +++ b/db/seeds/posts.yml @@ -22,6 +22,14 @@ help_category: About Codidact community_id: ~ +- post_type_id: <%= PolicyDoc.post_type_id %> + title: Guidelines for promotional content in posts on Codidact sites + body_markdown: $FILE posts/spam.html + body: $FILE posts/spam.html + doc_slug: spam + help_category: About Codidact + community_id: ~ + - post_type_id: <%= HelpDoc.post_type_id %> title: FAQ body_markdown: $FILE posts/local_faq.html diff --git a/db/seeds/posts/spam.html b/db/seeds/posts/spam.html new file mode 100644 index 0000000000000000000000000000000000000000..0479665f6564700c4e9001197ded3faa66e6c0cf --- /dev/null +++ b/db/seeds/posts/spam.html @@ -0,0 +1,17 @@ +<p>It's natural to want to promote yourself. You want as many people as possible to know about your dragon-riding place (only 1,000 gold coins per ride!), and to make sure that you're their first choice for saddling up firebreathers. With that in mind, though, we do need to set some restrictions and guidelines on self-promotion here.</p> + +<p>When promoting or linking anything that you are affiliated with, please keep the following points in mind:</p> + +<ul> + <li> + <p><strong>You <em>must</em> explicitly state your affiliation</strong>. If you're linking your dragon-riding place, please just include a disclaimer that clearly states how you're connected with it. Something as simple as "This place is run by my half-brother Gronk, and I work there" is perfectly fine.</p> + </li> + <li> + <p><strong>Please don't include promotional content when it's not necessary</strong>. If it's not <em>necessary</em> for your post, then just leave it out. If you're answering a question about gnomes in the garden, your dragon-riding business isn't relevant no matter how well your half-brother Gronk keeps the gnomes out. Keep your answer focused, and don't include self-promotion when it isn't relevant.</p> + </li> + <li> + <p><strong>Your posts should not be limited to only the promotional content</strong>. If <em>every single post</em> that you write is about your dragon-riding business, you're probably doing something wrong. While a few links or mentions here and there is fine in moderation, if that's all you ever post about you should probably think about why you're posting here in the first place. (Hint: If it's just to self-promote, that's a bad sign.) + </li> +</ul> + +<p>Posts that don't follow the above guidelines may be deleted, even without warning.</p> \ No newline at end of file diff --git a/db/seeds/warning_templates.yml b/db/seeds/warning_templates.yml new file mode 100644 index 0000000000000000000000000000000000000000..8487f011430754c4496740abf548bdb3a638a16c --- /dev/null +++ b/db/seeds/warning_templates.yml @@ -0,0 +1,11 @@ +- name: Spam + body: $FILE warning_templates/spam.md + active: true + +- name: Rudeness + body: $FILE warning_templates/rudeness.md + active: true + +- name: Many off-topic posts + body: $FILE warning_templates/off-topic.md + active: true \ No newline at end of file diff --git a/db/seeds/warning_templates/off-topic.md b/db/seeds/warning_templates/off-topic.md new file mode 100644 index 0000000000000000000000000000000000000000..2002a6db0c501c7fbd9c5ec2711182ce48a59e67 --- /dev/null +++ b/db/seeds/warning_templates/off-topic.md @@ -0,0 +1,9 @@ +We've noticed that you've written a number of posts about topics that are beyond the scope of this site. + +You can find out about what's on-topic and what's off-topic on $SiteName in the [help center](/help/faq). + +This is just a gentle reminder that we expect posts on this site to stay focused on the topic on-hand. +We have a [Network of sites](https://codidact.com/) that you are free to use; you may find one of our other sites more suitable to some of your posts. If you would like to talk about the possibility of creating a site for a subject not currently covered, please write a post on [meta](https://meta.codidact.com/categories/10) with your request. +Additionally, we have a $ChatLink available for more free-form discussion. + +While we appreciate your continued contributions within the scope of this site, we do ask that you make sure that the topic of your posts remain in scope. \ No newline at end of file diff --git a/db/seeds/warning_templates/rudeness.md b/db/seeds/warning_templates/rudeness.md new file mode 100644 index 0000000000000000000000000000000000000000..86c4d9317206d4cece63ad1032a97fefe2b0cf79 --- /dev/null +++ b/db/seeds/warning_templates/rudeness.md @@ -0,0 +1,7 @@ +We've noticed that you've had some recent interactions on this site that have been less than polite. + +In keeping with our [Code of Conduct](/policy/code-of-conduct), we expect everyone using this site to remain respectful, presume good intent, and use common sense when communicating with other members of the site. + +We understand that a single person is rarely solely to blame when discussions get heated, and that you may feel that you are not at fault. However, please remember that someone else being out of line does not excuse similar behavior on your part. If someone is being disrespectful or rude, please raise a flag on the comment or post and then disengage from the interaction. A moderator will handle your flag and review the situation. + +While we appreciate your continued contributions to $SiteName, we do ask that you stay mindful of your fellow users and abide by the Code of Conduct. \ No newline at end of file diff --git a/db/seeds/warning_templates/spam.md b/db/seeds/warning_templates/spam.md new file mode 100644 index 0000000000000000000000000000000000000000..b8cc6fff819e830f6c13be8d003f65064cec2008 --- /dev/null +++ b/db/seeds/warning_templates/spam.md @@ -0,0 +1,7 @@ +We've noticed that in addition to your contributions to $SiteName, you've also been using this account to promote a product, service, or similar. + +This is a gentle reminder that using an account for promotional purposes is against the rules of this site. + +Please take a moment to review the guidelines for promotional content here on $SiteName, which can be found in the [help center](/policy/spam). + +While we appreciate your continued contributions to $SiteName, we do ask that you stay within the boundaries of the rules about promotional content. \ No newline at end of file diff --git a/test/controllers/mod_warning_controller_test.rb b/test/controllers/mod_warning_controller_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..d839d8c6a2d46184539ea93e5e3aac23db6bc42b --- /dev/null +++ b/test/controllers/mod_warning_controller_test.rb @@ -0,0 +1,61 @@ +require 'test_helper' + +class ModWarningControllerTest < ActionController::TestCase + include Devise::Test::ControllerHelpers + + test 'should require authentication to access pages' do + sign_out :user + [:log, :new].each do |path| + get path, params: { user_id: users(:standard_user).id } + assert_response(404) + end + end + + test 'should require moderator status to access pages' do + sign_in users(:standard_user) + [:log, :new].each do |path| + get path, params: { user_id: users(:standard_user).id } + assert_response(404) + end + end + + test 'suspended user should redirect to current warning page' do + sign_in users(:standard_user) + mod_warnings(:first_warning).update(active: true) + + current_controller = @controller + @controller = CategoriesController.new + get :homepage + @controller = current_controller + + assert_redirected_to '/warning' + mod_warnings(:first_warning).update(active: false) + end + + test 'warned user should be able to accept warning' do + sign_in users(:standard_user) + @warning = mod_warnings(:first_warning) + @warning.update(active: true) + post :approve, params: { approve_checkbox: true } + @warning.reload + assert !@warning.active + end + + test 'suspended user should not be able to accept pending suspension' do + sign_in users(:standard_user) + @warning = mod_warnings(:third_warning) + @warning.update(active: true) + post :approve, params: { approve_checkbox: true } + @warning.reload + assert @warning.active + end + + test 'suspended user should be able to accept outdated suspension' do + sign_in users(:standard_user) + @warning = mod_warnings(:second_warning) + @warning.update(active: true) + post :approve, params: { approve_checkbox: true } + @warning.reload + assert !@warning.active + end +end diff --git a/test/fixtures/mod_warnings.yml b/test/fixtures/mod_warnings.yml new file mode 100644 index 0000000000000000000000000000000000000000..11a1dff2febf6eb909f03385eefce2574b70c6c7 --- /dev/null +++ b/test/fixtures/mod_warnings.yml @@ -0,0 +1,24 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +first_warning: + community_user: sample_standard_user + body: NOBODY EXPECTS THE SPANISH INQUISITION + is_suspension: false + active: false + author: moderator + +second_warning: + community_user: sample_standard_user + body: NOBODY EXPECTS THE SPANISH INQUISITION! + is_suspension: true + suspension_end: 2000-01-01T00:00:00.000000Z + active: false + author: moderator + +third_warning: + community_user: sample_standard_user + body: NOBODY EXPECTS THE SPANISH INQUISITION!!! + is_suspension: true + suspension_end: 5000-01-01T00:00:00.000000Z + active: false + author: moderator \ No newline at end of file diff --git a/test/fixtures/warning_templates.yml b/test/fixtures/warning_templates.yml new file mode 100644 index 0000000000000000000000000000000000000000..80aed36e30b2598726b55a90c65850a8f9aeb609 --- /dev/null +++ b/test/fixtures/warning_templates.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the '{}' from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/test/models/mod_warning_test.rb b/test/models/mod_warning_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..f1155ecbc18b9f8fc80e26cd329ee7bcb1710cc2 --- /dev/null +++ b/test/models/mod_warning_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class ModWarningTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/warning_template_test.rb b/test/models/warning_template_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..4fa5886c239b89d2b93edc94274d05604d4c7c06 --- /dev/null +++ b/test/models/warning_template_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class WarningTemplateTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end