Skip to content

Commit

Permalink
Merge pull request #194 from Retrospring/refactor/user-bans
Browse files Browse the repository at this point in the history
Implement ban history
  • Loading branch information
raccube committed Jan 6, 2022
2 parents 75d24db + b398265 commit 96e48a8
Show file tree
Hide file tree
Showing 18 changed files with 391 additions and 106 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ gem "hcaptcha", "~> 6.0", git: "https://github.com/Retrospring/hcaptcha.git", re

gem "rolify", "~> 5.2"

gem "dry-initializer", "~> 3.0"
gem "dry-types", "~> 1.4"

gem 'ruby-progressbar'

gem 'rails_admin'
Expand Down
21 changes: 21 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,25 @@ GEM
docile (1.4.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dry-configurable (0.12.1)
concurrent-ruby (~> 1.0)
dry-core (~> 0.5, >= 0.5.0)
dry-container (0.8.0)
concurrent-ruby (~> 1.0)
dry-configurable (~> 0.1, >= 0.1.3)
dry-core (0.7.1)
concurrent-ruby (~> 1.0)
dry-inflector (0.2.1)
dry-initializer (3.0.4)
dry-logic (1.2.0)
concurrent-ruby (~> 1.0)
dry-core (~> 0.5, >= 0.5)
dry-types (1.5.1)
concurrent-ruby (~> 1.0)
dry-container (~> 0.3)
dry-core (~> 0.5, >= 0.5)
dry-inflector (~> 0.1, >= 0.1.2)
dry-logic (~> 1.0, >= 1.0.2)
equalizer (0.0.11)
erubi (1.10.0)
excon (0.89.0)
Expand Down Expand Up @@ -589,6 +608,8 @@ DEPENDENCIES
devise (~> 4.0)
devise-async
devise-i18n
dry-initializer (~> 3.0)
dry-types (~> 1.4)
factory_bot_rails
fake_email_validator
faker
Expand Down
58 changes: 31 additions & 27 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -154,64 +154,71 @@ namespace :justask do
fail "screen name required" if args[:screen_name].nil?
user = User.find_by_screen_name(args[:screen_name])
fail "user #{args[:screen_name]} not found" if user.nil?
user.permanently_banned = true
user.ban_reason = args[:reason]
user.save!
UseCase::User::Ban.call(
target_user_id: user.id,
expiry: nil,
reason: args[:reason],
)
puts "#{user.screen_name} got hit by\033[5m YE OLDE BANHAMMER\033[0m!!1!"
end

desc "Hits an user with the banhammer for one day."
task :ban, [:screen_name, :reason] => :environment do |t, args|
fail "screen name required" if args[:screen_name].nil?
user = User.find_by_screen_name(args[:screen_name])
user.permanently_banned = false
user.banned_until = DateTime.current + 1
user.ban_reason = args[:reason]
user.save!
UseCase::User::Ban.call(
target_user_id: user.id,
expiry: DateTime.current + 1,
reason: args[:reason],
)
puts "#{user.screen_name} got hit by\033[5m YE OLDE BANHAMMER\033[0m!!1!"
end

desc "Hits an user with the banhammer for one week."
task :week_ban, [:screen_name, :reason] => :environment do |t, args|
fail "screen name required" if args[:screen_name].nil?
user = User.find_by_screen_name(args[:screen_name])
user.permanently_banned = false
user.banned_until = DateTime.current + 7
user.ban_reason = args[:reason]
user.save!
UseCase::User::Ban.call(
target_user_id: user.id,
expiry: DateTime.current + 7,
reason: args[:reason],
)
puts "#{user.screen_name} got hit by\033[5m YE OLDE BANHAMMER\033[0m!!1!"
end

desc "Hits an user with the banhammer for one month."
task :month_ban, [:screen_name, :reason] => :environment do |t, args|
fail "screen name required" if args[:screen_name].nil?
user = User.find_by_screen_name(args[:screen_name])
user.permanently_banned = false
user.banned_until = DateTime.current + 30
user.ban_reason = args[:reason]
user.save!
UseCase::User::Ban.call(
target_user_id: user.id,
expiry: DateTime.current + 30,
reason: args[:reason],
)
puts "#{user.screen_name} got hit by\033[5m YE OLDE BANHAMMER\033[0m!!1!"
end

desc "Hits an user with the banhammer for one year."
task :year_ban, [:screen_name, :reason] => :environment do |t, args|
fail "screen name required" if args[:screen_name].nil?
user = User.find_by_screen_name(args[:screen_name])
user.permanently_banned = false
user.banned_until = DateTime.current + 365
user.ban_reason = args[:reason]
user.save!
UseCase::User::Ban.call(
target_user_id: user.id,
expiry: DateTime.current + 365,
reason: args[:reason],
)
puts "#{user.screen_name} got hit by\033[5m YE OLDE BANHAMMER\033[0m!!1!"
end

desc "Hits an user with the banhammer for one aeon."
task :aeon_ban, [:screen_name, :reason] => :environment do |t, args|
fail "screen name required" if args[:screen_name].nil?
user = User.find_by_screen_name(args[:screen_name])
user.permanently_banned = false
user.banned_until = DateTime.current + 365_000_000_000
user.ban_reason = args[:reason]
user.save!
UseCase::User::Ban.call(
target_user_id: user.id,
expiry: DateTime.current + 365_000_000_000,
reason: args[:reason],
)
puts "#{user.screen_name} got hit by\033[5m YE OLDE BANHAMMER\033[0m!!1!"
end

Expand All @@ -220,10 +227,7 @@ namespace :justask do
fail "screen name required" if args[:screen_name].nil?
user = User.find_by_screen_name(args[:screen_name])
fail "user #{args[:screen_name]} not found" if user.nil?
user.permanently_banned = false
user.banned_until = nil
user.ban_reason = nil
user.save!
UseCase::User::Unban.call(user.id)
puts "#{user.screen_name} is no longer banned."
end

Expand Down
47 changes: 32 additions & 15 deletions app/controllers/ajax/moderation_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
require 'use_case/user/ban'
require 'use_case/user/unban'
require 'errors'

class Ajax::ModerationController < AjaxController
def vote
params.require :id
Expand Down Expand Up @@ -108,36 +112,49 @@ def ban

params.require :user
params.require :ban
params.require :permaban

reason = params[:reason]
target = User.find_by_screen_name!(params[:user])
unban = params[:ban] == "0"
perma = params[:permaban] == "1"

buntil = DateTime.strptime params[:until], "%m/%d/%Y %I:%M %p" unless unban || perma
duration = params[:duration].to_i
duration_unit = params[:duration_unit].to_s
reason = params[:reason].to_s
target_user = User.find_by_screen_name!(params[:user])
unban = params[:ban] == '0'
perma = params[:duration].blank?

if !unban && target.has_role?(:administrator)
if !unban && target_user.has_role?(:administrator)
@response[:status] = :nopriv
@response[:message] = I18n.t('messages.moderation.ban.nopriv')
return
end

if unban
target.unban
UseCase::User::Unban.call(target_user.id)
@response[:message] = I18n.t('messages.moderation.ban.unban')
@response[:success] = true
@response[:status] = :okay
return
elsif perma
target.ban nil, reason
@response[:message] = I18n.t('messages.moderation.ban.perma')
expiry = nil
else
target.ban buntil, reason
@response[:message] = I18n.t('messages.moderation.ban.temp', date: buntil.to_s)
params.require :duration
params.require :duration_unit

raise Errors::InvalidBanDuration unless %w[hours days weeks months].include? duration_unit

expiry = DateTime.now + duration.public_send(duration_unit)
@response[:message] = I18n.t('messages.moderation.ban.temp', date: expiry.to_s)
end
target.save!

UseCase::User::Ban.call(
target_user_id: target_user.id,
expiry: expiry,
reason: reason,
source_user_id: current_user.id)

target_user.save!

@response[:status] = :okay
@response[:success] = target.banned? == !unban
@response[:success] = true
end

def privilege
Expand All @@ -162,7 +179,7 @@ def privilege

@response[:checked] = status
type = params[:type].downcase
target_role = {"admin" => "administrator"}.fetch(type, type).to_sym
target_role = {'admin' => 'administrator'}.fetch(type, type).to_sym

if status
target_user.add_role target_role
Expand Down
7 changes: 4 additions & 3 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ def banned?
name = current_user.screen_name
# obligatory '2001: A Space Odyssey' reference
flash[:notice] = t('flash.ban.error', name: name)
unless current_user.ban_reason.nil?
flash[:notice] += "\n#{t('flash.ban.reason', reason: current_user.ban_reason)}"
current_ban = current_user.bans.current.first
unless current_ban&.reason.nil?
flash[:notice] += "\n#{t('flash.ban.reason', reason: current_user.bans.current.first.reason)}"
end
if not current_user.permanently_banned?
unless current_ban&.permanently_banned?
# TODO format banned_until
flash[:notice] += "\n#{t('flash.ban.until', time: current_user.banned_until)}"
end
Expand Down
44 changes: 22 additions & 22 deletions app/javascript/legacy/moderation/ban.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,43 @@ load = ->
banCheckbox = modalForm.querySelector('[name="ban"][type="checkbox"]')
permabanCheckbox = modalForm.querySelector('[name="permaban"][type="checkbox"]')

banCheckbox.addEventListener "change", (event) ->
$t = $ this
if $t.is(":checked")
$("#ban-controls").show()
else
$("#ban-controls").hide()
permabanCheckbox.addEventListener "change", (event) ->
$t = $ this
if $t.is(":checked")
$("#ban-controls-time").hide()
else
$("#ban-controls-time").show()
if banCheckbox
banCheckbox.addEventListener "change", (event) ->
$t = $ this
if $t.is(":checked")
$("#ban-controls").show()
else
$("#ban-controls").hide()
permabanCheckbox.addEventListener "change", (event) ->
$t = $ this
if $t.is(":checked")
$("#ban-controls-time").hide()
else
$("#ban-controls-time").show()

modalForm.addEventListener "submit", (event) ->
event.preventDefault();

checktostr = (el) ->
if el.checked
"1"
else
"0"

data = {
ban: checktostr banCheckbox
permaban: checktostr permabanCheckbox
until: modalForm.elements["until"].value.trim()
reason: modalForm.elements["reason"].value.trim()
ban: "0"
user: modalForm.elements["user"].value
}

if banCheckbox && banCheckbox.checked
data.ban = "1"
data.reason = modalForm.elements["reason"].value.trim()
unless permabanCheckbox.checked
data.duration = modalForm.elements["duration"].value.trim()
data.duration_unit = modalForm.elements["duration_unit"].value.trim()

$.ajax
url: '/ajax/mod/ban'
type: 'POST'
data: data
success: (data, status, jqxhr) ->
showNotification data.message, data.success
error: (jqxhr, status, error) ->
console.error 'request failed', data
console.log jqxhr, status, error
showNotification translate('frontend.error.message'), false
complete: (jqxhr, status) ->
Expand Down
29 changes: 22 additions & 7 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ class User < ApplicationRecord
has_one :profile, dependent: :destroy
has_one :theme, dependent: :destroy

has_many :bans, class_name: 'UserBan', dependent: :destroy
has_many :banned_users, class_name: 'UserBan',
foreign_key: 'banned_by_id',
dependent: :nullify

SCREEN_NAME_REGEX = /\A[a-zA-Z0-9_]{1,16}\z/
WEBSITE_REGEX = /https?:\/\/([A-Za-z.\-]+)\/?(?:.*)/i

Expand Down Expand Up @@ -218,21 +223,31 @@ def report_comment(report, content)
end
# endregion

# forwards fill
def banned?
self.permanently_banned? or ((not self.banned_until.nil?) and self.banned_until >= DateTime.current)
self.bans.current.count > 0
end

def unban
self.update(permanently_banned: false, ban_reason: nil, banned_until: nil)
UseCase::User::Unban.call(id)
end

def ban(buntil=nil, reason=nil)
if buntil == nil
self.update(permanently_banned: true, ban_reason: reason)
# Bans a user.
# @param duration [Integer?] Ban duration
# @param duration_unit [String, nil] Unit for the <code>duration</code> parameter. Accepted units: hours, days, weeks, months
# @param reason [String] Reason for the ban. This is displayed to the user.
# @param banned_by [User] User who instated the ban
def ban(duration, duration_unit = 'hours', reason = nil, banned_by = nil)
if duration
expiry = duration.public_send(duration_unit)
else
self.update(permanently_banned: false, banned_until: buntil, ban_reason: reason)
expiry = nil
end
UseCase::User::Ban.call(
target_user_id: id,
expiry: expiry,
reason: reason,
source_user_id: banned_by&.id
)
end

def can_export?
Expand Down
6 changes: 6 additions & 0 deletions app/models/user_ban.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class UserBan < ApplicationRecord
belongs_to :user
belongs_to :banned_by, class_name: 'User'

scope :current, -> { where('expires_at IS NULL or expires_at > NOW()') }
end
Loading

0 comments on commit 96e48a8

Please sign in to comment.