From 29b98e5002d55ae615b970237e38994246153428 Mon Sep 17 00:00:00 2001 From: clragon Date: Mon, 13 Jan 2025 04:28:32 +0100 Subject: [PATCH] [StaffNotes] Add updating and deleting (#823) --- .rubocop.yml | 3 + .rubocop_todo.yml | 4 +- .../admin/staff_notes_controller.rb | 41 ---------- app/controllers/mod_actions_controller.rb | 13 ++-- app/controllers/staff_notes_controller.rb | 77 +++++++++++++++++++ app/decorators/mod_action_decorator.rb | 16 +++- app/javascript/packs/application.js | 1 + app/javascript/src/javascripts/staff_notes.js | 25 ++++++ .../src/styles/specific/staff_notes.scss | 12 ++- app/models/mod_action.rb | 44 ++++++++--- app/models/staff_note.rb | 58 ++++++++++++-- app/models/user.rb | 2 +- .../staff_notes/partials/_for_user.html.erb | 13 ---- .../partials/_list_of_notes.html.erb | 3 - .../admin/staff_notes/partials/_new.html.erb | 6 -- .../staff_notes/partials/_staff_note.html.erb | 19 ----- app/views/mod_actions/_search.html.erb | 2 +- .../{admin => }/staff_notes/_search.html.erb | 4 +- .../staff_notes/_secondary_links.html.erb | 4 + app/views/staff_notes/edit.html.erb | 13 ++++ .../{admin => }/staff_notes/index.html.erb | 4 +- .../{admin => }/staff_notes/new.html.erb | 4 +- app/views/staff_notes/partials/_edit.html.erb | 6 ++ .../staff_notes/partials/_for_user.html.erb | 15 ++++ .../partials/_list_of_notes.html.erb | 3 + app/views/staff_notes/partials/_new.html.erb | 6 ++ .../staff_notes/partials/_staff_note.html.erb | 39 ++++++++++ app/views/staff_notes/search.html.erb | 13 ++++ app/views/staff_notes/show.html.erb | 13 ++++ app/views/static/site_map.html.erb | 3 + app/views/users/show.html.erb | 2 +- config/routes.rb | 12 ++- ...240205174652_soft_deletable_staff_notes.rb | 9 +++ db/structure.sql | 19 ++++- .../user_feedbacks_controller_test.rb | 8 +- 35 files changed, 387 insertions(+), 129 deletions(-) delete mode 100644 app/controllers/admin/staff_notes_controller.rb create mode 100644 app/controllers/staff_notes_controller.rb create mode 100644 app/javascript/src/javascripts/staff_notes.js delete mode 100644 app/views/admin/staff_notes/partials/_for_user.html.erb delete mode 100644 app/views/admin/staff_notes/partials/_list_of_notes.html.erb delete mode 100644 app/views/admin/staff_notes/partials/_new.html.erb delete mode 100644 app/views/admin/staff_notes/partials/_staff_note.html.erb rename app/views/{admin => }/staff_notes/_search.html.erb (55%) create mode 100644 app/views/staff_notes/_secondary_links.html.erb create mode 100644 app/views/staff_notes/edit.html.erb rename app/views/{admin => }/staff_notes/index.html.erb (70%) rename app/views/{admin => }/staff_notes/new.html.erb (59%) create mode 100644 app/views/staff_notes/partials/_edit.html.erb create mode 100644 app/views/staff_notes/partials/_for_user.html.erb create mode 100644 app/views/staff_notes/partials/_list_of_notes.html.erb create mode 100644 app/views/staff_notes/partials/_new.html.erb create mode 100644 app/views/staff_notes/partials/_staff_note.html.erb create mode 100644 app/views/staff_notes/search.html.erb create mode 100644 app/views/staff_notes/show.html.erb create mode 100644 db/migrate/20240205174652_soft_deletable_staff_notes.rb diff --git a/.rubocop.yml b/.rubocop.yml index 6a4974108..33c934fbc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -156,3 +156,6 @@ Style/TrailingCommaInArrayLiteral: Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: consistent_comma + +Rails/NotNullColumn: + Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f26e4fa29..4ebf69619 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -498,7 +498,7 @@ Layout/SpaceInsideBlockBraces: # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideHashLiteralBraces: Exclude: - - 'app/controllers/admin/staff_notes_controller.rb' + - 'app/controllers/staff_notes_controller.rb' - 'app/controllers/admin/users_controller.rb' - 'app/controllers/application_controller.rb' - 'app/controllers/comment_votes_controller.rb' @@ -2416,7 +2416,7 @@ Style/StringLiterals: Exclude: - 'Gemfile' - 'Rakefile' - - 'app/controllers/admin/staff_notes_controller.rb' + - 'app/controllers/staff_notes_controller.rb' - 'app/controllers/blips_controller.rb' - 'app/controllers/comments_controller.rb' - 'app/controllers/edit_histories_controller.rb' diff --git a/app/controllers/admin/staff_notes_controller.rb b/app/controllers/admin/staff_notes_controller.rb deleted file mode 100644 index 2b5fad79f..000000000 --- a/app/controllers/admin/staff_notes_controller.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -module Admin - class StaffNotesController < ApplicationController - before_action :can_view_staff_notes_only - respond_to :html - - def index - @user = User.find_by(id: params[:user_id]) - @notes = StaffNote.search(search_params.merge({ user_id: params[:user_id] })).includes(:user, :creator).paginate(params[:page], limit: params[:limit]) - respond_with(@notes) - end - - def new - @user = User.find(params[:user_id]) - @staff_note = StaffNote.new(note_params) - respond_with(@note) - end - - def create - @user = User.find(params[:user_id]) - @staff_note = StaffNote.create(note_params.merge({creator: CurrentUser.user, user_id: @user.id})) - flash[:notice] = @staff_note.valid? ? "Staff Note added" : @staff_note.errors.full_messages.join("; ") - respond_with(@staff_note) do |format| - format.html do - redirect_back fallback_location: admin_staff_notes_path - end - end - end - - private - - def search_params - permit_search_params(%i[creator_id creator_name user_id user_name resolved body_matches without_system_user]) - end - - def note_params - params.fetch(:staff_note, {}).permit(%i[body]) - end - end -end diff --git a/app/controllers/mod_actions_controller.rb b/app/controllers/mod_actions_controller.rb index b3fd2076b..c13f61348 100644 --- a/app/controllers/mod_actions_controller.rb +++ b/app/controllers/mod_actions_controller.rb @@ -5,19 +5,20 @@ class ModActionsController < ApplicationController def index @mod_actions = ModActionDecorator.decorate_collection( - ModAction.includes(:creator).search(search_params).paginate(params[:page], limit: params[:limit]), + ModAction.visible(CurrentUser.user).includes(:creator).search(search_params).paginate(params[:page], limit: params[:limit]), ) - respond_with(@mod_actions) do |format| - format.json do - render json: @mod_actions.to_json - end - end + respond_with(@mod_actions) end def show @mod_action = ModAction.find(params[:id]) + check_permission(@mod_action) respond_with(@mod_action) do |fmt| fmt.html { redirect_to mod_actions_path(search: { id: @mod_action.id }) } end end + + def check_permission(mod_action) + raise(User::PrivilegeError) unless mod_action.can_view?(CurrentUser.user) + end end diff --git a/app/controllers/staff_notes_controller.rb b/app/controllers/staff_notes_controller.rb new file mode 100644 index 000000000..cefaac26b --- /dev/null +++ b/app/controllers/staff_notes_controller.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +class StaffNotesController < ApplicationController + before_action :can_view_staff_notes_only + before_action :load_staff_note, only: %i[show edit update delete undelete] + before_action :check_edit_privilege, only: %i[update] + before_action :check_delete_privilege, only: %i[delete undelete] + respond_to :html, :json + + def index + @user = User.find_by(id: params[:user_id]) + @notes = StaffNote.search(search_params.merge({ user_id: params[:user_id] })).includes(:user, :creator).paginate(params[:page], limit: params[:limit]) + respond_with(@notes) + end + + def show + respond_with(@staff_note) + end + + def new + @user = User.find(params[:user_id]) + @staff_note = StaffNote.new(staff_note_params) + respond_with(@note) + end + + def edit + respond_with(@staff_note) + end + + def create + @user = User.find(params[:user_id]) + @staff_note = StaffNote.create(staff_note_params.merge({ user_id: @user.id })) + flash[:notice] = @staff_note.valid? ? "Staff Note added" : @staff_note.errors.full_messages.join("; ") + respond_with(@staff_note) do |format| + format.html do + redirect_back fallback_location: staff_notes_path + end + end + end + + def update + @staff_note.update(staff_note_params) + redirect_back(fallback_location: staff_notes_path) + end + + def delete + @staff_note.update(is_deleted: true) + redirect_back(fallback_location: staff_notes_path) + end + + def undelete + @staff_note.update(is_deleted: false) + redirect_back(fallback_location: staff_notes_path) + end + + private + + def search_params + permit_search_params(%i[creator_id creator_name updater_id updater_name user_id user_name body_matches without_system_user include_deleted]) + end + + def staff_note_params + params.fetch(:staff_note, {}).permit(%i[body]) + end + + def load_staff_note + @staff_note = StaffNote.find(params[:id]) + end + + def check_edit_privilege + raise User::PrivilegeError unless @staff_note.can_edit?(CurrentUser.user) + end + + def check_delete_privilege + raise User::PrivilegeError unless @staff_note.can_delete?(CurrentUser.user) + end +end diff --git a/app/decorators/mod_action_decorator.rb b/app/decorators/mod_action_decorator.rb index 45d12b184..a0b7f71c0 100644 --- a/app/decorators/mod_action_decorator.rb +++ b/app/decorators/mod_action_decorator.rb @@ -55,7 +55,7 @@ class ModActionDecorator < ApplicationDecorator when "artist_delete" "Deleted artist ##{vals['artist_id']} (#{vals['artist_name']})" when "artist_page_rename" - "Renamed artist page (\"#{vals['old_name']}\":/artists/show_or_new?name=#{vals['old_name']} -> \"#{vals['new_name']}\":/artists/show_or_new?name=#{vals['new_name']})" + "Renamed artist page (\"#{vals['old_name']}\":/artists/show_or_new?name=#{vals['old_name']} โ†’ \"#{vals['new_name']}\":/artists/show_or_new?name=#{vals['new_name']})" when "artist_page_lock" "Locked artist page artist ##{vals['artist_page']}" when "artist_page_unlock" @@ -77,6 +77,16 @@ class ModActionDecorator < ApplicationDecorator when "avoid_posting_undelete" "Undeleted \"avoid posting ##{vals['id']}\":/avoid_postings/#{vals['id']} for [[#{vals['artist_name']}]]" + ### Staff Note ### + when "staff_note_create" + "Created \"staff note ##{vals['id']}\":/staff_notes/#{vals['id']} for #{user}\n#{vals['body']}" + when "staff_note_update" + "Updated \"staff note ##{vals['id']}\":/staff_notes/#{vals['id']} for #{user}\n#{vals['body']}" + when "staff_note_delete" + "Deleted \"staff note ##{vals['id']}\":/staff_notes/#{vals['id']} for #{user}" + when "staff_note_undelete" + "Undeleted \"staff note ##{vals['id']}\":/staff_notes/#{vals['id']} for #{user}" + ### User ### when "user_delete" @@ -276,7 +286,7 @@ class ModActionDecorator < ApplicationDecorator ### BURs ### when "mass_update" - "Mass updated [[#{vals['antecedent']}]] -> [[#{vals['consequent']}]]" + "Mass updated [[#{vals['antecedent']}]] โ†’ [[#{vals['consequent']}]]" when "nuke_tag" "Nuked tag [[#{vals['tag_name']}]]" @@ -319,7 +329,7 @@ class ModActionDecorator < ApplicationDecorator "Edited whitelist entry" else if vals['old_pattern'] && vals['old_pattern'] != vals['pattern'] && CurrentUser.is_admin? - "Edited whitelist entry '#{vals['old_pattern']}' -> '#{vals['pattern']}'" + "Edited whitelist entry '#{vals['old_pattern']}' โ†’ '#{vals['pattern']}'" else "Edited whitelist entry '#{CurrentUser.is_admin? ? vals['pattern'] : vals['note']}'" end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 1d30f168f..b04f50f6d 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -47,6 +47,7 @@ export { default as PostReplacement } from '../src/javascripts/post_replacement. export { default as PostVersions } from '../src/javascripts/post_versions.js'; export { default as Replacer } from '../src/javascripts/replacer.js'; export { default as Shortcuts } from '../src/javascripts/shortcuts.js'; +export { default as StaffNote } from '../src/javascripts/staff_notes.js'; export { default as Utility } from '../src/javascripts/utility.js'; export { default as TagRelationships } from '../src/javascripts/tag_relationships.js'; export { default as Takedown } from '../src/javascripts/takedowns.js'; diff --git a/app/javascript/src/javascripts/staff_notes.js b/app/javascript/src/javascripts/staff_notes.js new file mode 100644 index 000000000..2122ad53b --- /dev/null +++ b/app/javascript/src/javascripts/staff_notes.js @@ -0,0 +1,25 @@ +const StaffNote = {}; + +StaffNote.initialize_all = function () { + $(".expand-new-staff-note").on("click", StaffNote.show_new_note_form); + $(".edit-staff-note-link").on("click", StaffNote.show_edit_form); +}; + +StaffNote.show_new_note_form = function (e) { + e.preventDefault(); + $(e.target).hide(); + var $form = $(e.target).closest("div.new-staff-note").find("form"); + $form.show(); + $form[0].scrollIntoView(false); +}; + +StaffNote.show_edit_form = function (e) { + e.preventDefault(); + $(this).closest(".staff-note").find(".edit_staff_note").show(); +}; + +$(document).ready(function () { + StaffNote.initialize_all(); +}); + +export default StaffNote; diff --git a/app/javascript/src/styles/specific/staff_notes.scss b/app/javascript/src/styles/specific/staff_notes.scss index 82213752a..f03111552 100644 --- a/app/javascript/src/styles/specific/staff_notes.scss +++ b/app/javascript/src/styles/specific/staff_notes.scss @@ -1,11 +1,15 @@ .staff-note-list { - .staff-note { - margin-bottom: $padding-050; - } - // This is getting overwritten through DText styling on user profiles, // because the whole list is wrapped in a collapsable section. h4.author-name { font-size: $h4-size; } } + +.staff-note { + margin-bottom: $padding-050; + + &[data-is-deleted="true"] { + background: $background-article-hidden; + } +} diff --git a/app/models/mod_action.rb b/app/models/mod_action.rb index 02b35eb36..0ee0cb942 100644 --- a/app/models/mod_action.rb +++ b/app/models/mod_action.rb @@ -16,6 +16,10 @@ class ModAction < ApplicationRecord avoid_posting_delete: %i[id artist_name], avoid_posting_undelete: %i[id artist_name], avoid_posting_destroy: %i[id artist_name], + staff_note_create: %i[id user_id body], + staff_note_update: %i[id user_id body old_body], + staff_note_delete: %i[id user_id], + staff_note_undelete: %i[id user_id], blip_delete: %i[blip_id user_id], blip_hide: %i[blip_id user_id], blip_unhide: %i[blip_id user_id], @@ -91,18 +95,38 @@ class ModAction < ApplicationRecord takedown_process: %i[takedown_id], }.freeze + ProtectedActionKeys = %w[staff_note_create staff_note_update staff_note_delete staff_note_undelete ip_ban_create ip_ban_delete].freeze + KnownActionKeys = KnownActions.keys.freeze - def self.search(params) - q = super - - q = q.where_user(:creator_id, :creator, params) - - if params[:action].present? - q = q.where("action = ?", params[:action]) + module SearchMethods + def visible(user) + if user.is_staff? + all + else + where.not(action: ProtectedActionKeys) + end end - q.apply_basic_order(params) + def search(params) + q = super + + q = q.where_user(:creator_id, :creator, params) + + if params[:action].present? + q = q.where("action = ?", params[:action]) + end + + q.apply_basic_order(params) + end + end + + def can_view?(user) + if user.is_staff? + true + else + ProtectedActionKeys.exclude?(action) + end end def values @@ -131,7 +155,7 @@ class ModAction < ApplicationRecord end def hidden_attributes - super + [:values] + super + %i[values values_old] end def method_attributes @@ -145,4 +169,6 @@ class ModAction < ApplicationRecord def initialize_creator self.creator_id = CurrentUser.id end + + extend SearchMethods end diff --git a/app/models/staff_note.rb b/app/models/staff_note.rb index 496ba8957..5b3dfe0f5 100644 --- a/app/models/staff_note.rb +++ b/app/models/staff_note.rb @@ -1,8 +1,33 @@ # frozen_string_literal: true class StaffNote < ApplicationRecord - belongs_to :creator, class_name: "User" + belongs_to_creator + belongs_to_updater belongs_to :user + after_create :log_create + after_update :log_update + + scope :active, -> { where(is_deleted: false) } + + module LogMethods + def log_create + ModAction.log(:staff_note_create, { id: id, user_id: user_id, body: body }) + end + + def log_update + if saved_change_to_body? + ModAction.log(:staff_note_update, { id: id, user_id: user_id, body: body, old_body: body_before_last_save }) + end + + if saved_change_to_is_deleted? + if is_deleted? + ModAction.log(:staff_note_delete, { id: id, user_id: user_id }) + else + ModAction.log(:staff_note_undelete, { id: id, user_id: user_id }) + end + end + end + end module SearchMethods def search(params) @@ -12,28 +37,45 @@ class StaffNote < ApplicationRecord q = q.attribute_matches(:body, params[:body_matches]) q = q.where_user(:user_id, :user, params) q = q.where_user(:creator_id, :creator, params) + q = q.where_user(:updater_id, :updater, params) if params[:without_system_user]&.truthy? q = q.where.not(creator: User.system) end + if params[:is_deleted].present? + q = q.attribute_matches(:is_deleted, params[:is_deleted]) + elsif !params[:include_deleted]&.truthy? + q = q.active + end + q.apply_basic_order(params) end def default_order - order("resolved asc, id desc") + order("id desc") end end + include LogMethods extend SearchMethods - def resolve! - self.resolved = true - save + def user_name + User.id_to_name(user_id) end - def unresolve! - self.resolved = false - save + def user_name=(name) + self.user_id = User.name_to_id(name) + end + + def can_edit?(user) + return false unless user.is_staff? + user.id == creator_id || user.is_admin? + end + + def can_delete?(user) + return false unless user.is_staff? + return true if creator_id == user.id || user.is_admin? + user_id != user.id end end diff --git a/app/models/user.rb b/app/models/user.rb index b03d0c410..0ad1ecde7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -111,7 +111,7 @@ class User < ApplicationRecord has_many :post_sets, -> { order(name: :asc) }, foreign_key: :creator_id has_many :post_versions has_many :post_votes - has_many :staff_notes, -> { order("staff_notes.id desc") } + has_many :staff_notes, -> { active.order("staff_notes.id desc") } has_many :user_name_change_requests, -> { order(id: :asc) } belongs_to :avatar, class_name: 'Post', optional: true diff --git a/app/views/admin/staff_notes/partials/_for_user.html.erb b/app/views/admin/staff_notes/partials/_for_user.html.erb deleted file mode 100644 index ceb013bc3..000000000 --- a/app/views/admin/staff_notes/partials/_for_user.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -<% if CurrentUser.can_view_staff_notes? %> -
-
- Staff Notes (<%= user.staff_notes.count %>) -
-

<%= link_to "Staff Notes", user_staff_notes_path(user_id: user.id) %>

- <%= render "admin/staff_notes/partials/list_of_notes", staff_notes: user.staff_notes.limit(15), show_receiver_name: false %> -

New Staff Note

- <%= render "admin/staff_notes/partials/new", user: user, staff_note: StaffNote.new(user_id: user.id) %> -
-
-
-<% end %> diff --git a/app/views/admin/staff_notes/partials/_list_of_notes.html.erb b/app/views/admin/staff_notes/partials/_list_of_notes.html.erb deleted file mode 100644 index b2c0618b7..000000000 --- a/app/views/admin/staff_notes/partials/_list_of_notes.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -
- <%= render partial: "admin/staff_notes/partials/staff_note", collection: staff_notes, locals: { show_receiver_name: show_receiver_name } %> -
diff --git a/app/views/admin/staff_notes/partials/_new.html.erb b/app/views/admin/staff_notes/partials/_new.html.erb deleted file mode 100644 index 16c723bf2..000000000 --- a/app/views/admin/staff_notes/partials/_new.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -<%= error_messages_for :staff_note %> - -<%= custom_form_for(staff_note, url: user_staff_notes_path(user_id: user.id), method: :post) do |f| %> - <%= f.input :body, as: :dtext, label: false, allow_color: true %> - <%= f.button :submit, "Submit" %> -<% end %> diff --git a/app/views/admin/staff_notes/partials/_staff_note.html.erb b/app/views/admin/staff_notes/partials/_staff_note.html.erb deleted file mode 100644 index 89291fd20..000000000 --- a/app/views/admin/staff_notes/partials/_staff_note.html.erb +++ /dev/null @@ -1,19 +0,0 @@ -
-
-
-

<%= link_to_user staff_note.creator %>

- <%= staff_note.creator.level_string %> -
-
- <%= link_to time_ago_in_words_tagged(staff_note.created_at), user_staff_notes_path(user_id: staff_note.user_id, anchor: "staff-note-#{staff_note.id}") %> -
-
-
- <% if show_receiver_name %> -

On <%= link_to_user staff_note.user %>

- <% end %> -
- <%= format_text(staff_note.body, allow_color: true) %> -
-
-
diff --git a/app/views/mod_actions/_search.html.erb b/app/views/mod_actions/_search.html.erb index 47a17ff7e..02e91903c 100644 --- a/app/views/mod_actions/_search.html.erb +++ b/app/views/mod_actions/_search.html.erb @@ -1,4 +1,4 @@ <%= form_search(path: mod_actions_path) do |f| %> <%= f.user :creator %> - <%= f.input :action, label: "Action", collection: ModAction::KnownActionKeys.map {|k| [k.to_s.capitalize.tr("_"," "), k.to_s]}, include_blank: true %> + <%= f.input :action, label: "Action", collection: ModAction::KnownActionKeys.reject { |k| !CurrentUser.is_staff? && ModAction::ProtectedActionKeys.include?(k) }.map { |k| [k.to_s.capitalize.tr("_", " "), k.to_s] }, include_blank: true %> <% end %> diff --git a/app/views/admin/staff_notes/_search.html.erb b/app/views/staff_notes/_search.html.erb similarity index 55% rename from app/views/admin/staff_notes/_search.html.erb rename to app/views/staff_notes/_search.html.erb index acac7d11c..7c04574d5 100644 --- a/app/views/admin/staff_notes/_search.html.erb +++ b/app/views/staff_notes/_search.html.erb @@ -1,6 +1,8 @@ -<%= form_search path: admin_staff_notes_path, always_display: true do |f| %> +<%= form_search path: staff_notes_path do |f| %> <%= f.user :creator %> + <%= f.user :updater %> <%= f.user :user %> <%= f.input :body_matches, label: "Body" %> <%= f.input :without_system_user, as: :boolean, label: "Hide automated?" %> + <%= f.input :include_deleted, as: :boolean, label: "Show deleted?" %> <% end %> diff --git a/app/views/staff_notes/_secondary_links.html.erb b/app/views/staff_notes/_secondary_links.html.erb new file mode 100644 index 000000000..7e2991452 --- /dev/null +++ b/app/views/staff_notes/_secondary_links.html.erb @@ -0,0 +1,4 @@ +<% content_for(:secondary_links) do %> + <%= subnav_link_to "Listing", staff_notes_path %> + <%= subnav_link_to "Search", search_staff_notes_path %> +<% end %> diff --git a/app/views/staff_notes/edit.html.erb b/app/views/staff_notes/edit.html.erb new file mode 100644 index 000000000..a8fc2bd91 --- /dev/null +++ b/app/views/staff_notes/edit.html.erb @@ -0,0 +1,13 @@ +
+
+

Edit Staff Note For <%= link_to_user @staff_note.user %>

+ + <%= render "staff_notes/partials/edit", staff_note: @staff_note %> +
+
+ +<%= render "secondary_links" %> + +<% content_for(:page_title) do %> + Edit Staff Note +<% end %> diff --git a/app/views/admin/staff_notes/index.html.erb b/app/views/staff_notes/index.html.erb similarity index 70% rename from app/views/admin/staff_notes/index.html.erb rename to app/views/staff_notes/index.html.erb index bacfb3f47..63c0d1652 100644 --- a/app/views/admin/staff_notes/index.html.erb +++ b/app/views/staff_notes/index.html.erb @@ -7,12 +7,12 @@ <% end %> <%= render "search" %> - <%= render "admin/staff_notes/partials/list_of_notes", staff_notes: @notes, show_receiver_name: @user.blank? %> + <%= render "staff_notes/partials/list_of_notes", staff_notes: @notes, show_receiver_name: @user.blank? %> <%= numbered_paginator(@notes) %> -<%= render "users/secondary_links" %> +<%= render "secondary_links" %> <% content_for(:page_title) do %> <%= @user ? "#{@user.name} - Staff Notes" : "Staff Notes" %> diff --git a/app/views/admin/staff_notes/new.html.erb b/app/views/staff_notes/new.html.erb similarity index 59% rename from app/views/admin/staff_notes/new.html.erb rename to app/views/staff_notes/new.html.erb index afd4e5915..e1317777d 100644 --- a/app/views/admin/staff_notes/new.html.erb +++ b/app/views/staff_notes/new.html.erb @@ -2,11 +2,11 @@

New Staff Note for <%= link_to_user(@user) %>

- <%= render "admin/staff_notes/partials/new", staff_note: @staff_note, user: @user %> + <%= render "staff_notes/partials/new", staff_note: @staff_note, user: @user %>
-<%= render "users/secondary_links" %> +<%= render "secondary_links" %> <% content_for(:page_title) do %> New Staff Note diff --git a/app/views/staff_notes/partials/_edit.html.erb b/app/views/staff_notes/partials/_edit.html.erb new file mode 100644 index 000000000..8b2dbd9d0 --- /dev/null +++ b/app/views/staff_notes/partials/_edit.html.erb @@ -0,0 +1,6 @@ +<%= error_messages_for :staff_note %> + +<%= custom_form_for(staff_note, html: { style: ("display: none;" if local_assigns[:hidden]) }, url: staff_note_path(staff_note, user_id: staff_note.user_id), method: :patch) do |f| %> + <%= f.input :body, as: :dtext, label: false %> + <%= f.button :submit, "Submit" %> +<% end %> diff --git a/app/views/staff_notes/partials/_for_user.html.erb b/app/views/staff_notes/partials/_for_user.html.erb new file mode 100644 index 000000000..981b3fdc4 --- /dev/null +++ b/app/views/staff_notes/partials/_for_user.html.erb @@ -0,0 +1,15 @@ +<% if CurrentUser.can_view_staff_notes? %> +
+
+ Staff Notes (<%= user.staff_notes.count %>) +
+

<%= link_to "Staff Notes", staff_notes_path(search: { user_id: user.id }) %>

+ <%= render "staff_notes/partials/list_of_notes", staff_notes: user.staff_notes.limit(15), show_receiver_name: false %> +
+

<%= link_to "Create ยป", new_staff_note_path(search: { user_id: user.id }), class: "expand-new-staff-note" %>

+ <%= render "staff_notes/partials/new", user: user, staff_note: StaffNote.new(user_id: user.id), hidden: true %> +
+
+
+
+<% end %> diff --git a/app/views/staff_notes/partials/_list_of_notes.html.erb b/app/views/staff_notes/partials/_list_of_notes.html.erb new file mode 100644 index 000000000..9221d4519 --- /dev/null +++ b/app/views/staff_notes/partials/_list_of_notes.html.erb @@ -0,0 +1,3 @@ +
+ <%= render partial: "staff_notes/partials/staff_note", collection: staff_notes, locals: { show_receiver_name: show_receiver_name } %> +
diff --git a/app/views/staff_notes/partials/_new.html.erb b/app/views/staff_notes/partials/_new.html.erb new file mode 100644 index 000000000..6f76fe088 --- /dev/null +++ b/app/views/staff_notes/partials/_new.html.erb @@ -0,0 +1,6 @@ +<%= error_messages_for :staff_note %> + +<%= custom_form_for(staff_note, html: { style: ("display: none;" if local_assigns[:hidden]) }, url: staff_notes_path(user_id: user.id), method: :post) do |f| %> + <%= f.input :body, as: :dtext, label: false, allow_color: true %> + <%= f.button :submit, "Submit" %> +<% end %> diff --git a/app/views/staff_notes/partials/_staff_note.html.erb b/app/views/staff_notes/partials/_staff_note.html.erb new file mode 100644 index 000000000..ec2b34787 --- /dev/null +++ b/app/views/staff_notes/partials/_staff_note.html.erb @@ -0,0 +1,39 @@ +
+
+
+

<%= link_to_user staff_note.creator %>

+ <%= staff_note.creator.level_string %> +
+
+ <%= link_to time_ago_in_words_tagged(staff_note.created_at), staff_notes_path(search: { user_id: staff_note.user_id }, anchor: "staff-note-#{staff_note.id}") %> +
+
+
+ <% if show_receiver_name %> +

On <%= link_to_user staff_note.user %>

+ <% end %> +
+ <%= format_text(staff_note.body, allow_color: true) %> +
+ + <%= render "application/update_notice", record: staff_note %> +
+ + <% if staff_note.can_edit?(CurrentUser.user) %> +
  • <%= link_to "Edit", edit_staff_note_path(staff_note), id: "edit-staff-note-link-#{staff_note.id}", class: "edit-staff-note-link" %>
  • + <% end %> + <% if staff_note.can_delete?(CurrentUser.user) %> + <% if staff_note.is_deleted? %> +
  • <%= link_to "Undelete", undelete_staff_note_path(staff_note), method: :put %>
  • + <% else %> +
  • <%= link_to "Delete", delete_staff_note_path(staff_note), method: :put %>
  • + <% end %> + <% end %> +
    +
    + + <% if staff_note.can_edit?(CurrentUser.user) %> + <%= render "staff_notes/partials/edit", staff_note: staff_note, hidden: true %> + <% end %> +
    +
    diff --git a/app/views/staff_notes/search.html.erb b/app/views/staff_notes/search.html.erb new file mode 100644 index 000000000..a0b506abf --- /dev/null +++ b/app/views/staff_notes/search.html.erb @@ -0,0 +1,13 @@ +
    + +
    + +<%= render "secondary_links" %> + +<% content_for(:page_title) do %> + Search Staff Notes +<% end %> diff --git a/app/views/staff_notes/show.html.erb b/app/views/staff_notes/show.html.erb new file mode 100644 index 000000000..fca36fad2 --- /dev/null +++ b/app/views/staff_notes/show.html.erb @@ -0,0 +1,13 @@ +
    +
    +

    Staff Note For <%= link_to_user @staff_note.user %>

    + + <%= render "staff_notes/partials/staff_note", staff_note: @staff_note, show_receiver_name: true %> +
    +
    + +<%= render "secondary_links" %> + +<% content_for(:page_title) do %> + Staff Note - <%= @staff_note.user_name %> +<% end %> diff --git a/app/views/static/site_map.html.erb b/app/views/static/site_map.html.erb index 133b69b83..b906a5f3d 100644 --- a/app/views/static/site_map.html.erb +++ b/app/views/static/site_map.html.erb @@ -119,6 +119,9 @@
  • <%= link_to("Stuck DNP tags", new_admin_stuck_dnp_path) %>
  • <%= link_to("Destroyed Posts", admin_destroyed_posts_path) %>
  • <% end %> + <% if CurrentUser.is_staff? %> +
  • <%= link_to("Staff Notes", staff_notes_path) %>
  • + <% end %>
  • <%= link_to("Upload Whitelist", upload_whitelists_path) %>
  • <%= link_to("Mod Actions", mod_actions_path) %>
  • <%= link_to("Bulk Update Requests", bulk_update_requests_path) %>
  • diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index b8bcdb346..4a4dec43d 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,7 +1,7 @@
    <%= render "statistics", :presenter => @presenter, :user => @user %> - <%= render "admin/staff_notes/partials/for_user", user: @user %> + <%= render "staff_notes/partials/for_user", user: @user %> <%= render "posts/partials/common/inline_blacklist" %> <%= render "post_summary", presenter: @presenter, user: @user %> <%= render "about", presenter: @presenter, user: @user %> diff --git a/config/routes.rb b/config/routes.rb index 530c3b935..da6084b9f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -25,7 +25,6 @@ Rails.application.routes.draw do resource :reowner, controller: 'reowner', only: [:new, :create] resource :stuck_dnp, controller: "stuck_dnp", only: %i[new create] resources :destroyed_posts, only: %i[index show update] - resources :staff_notes, only: [:index] end namespace :security do @@ -95,6 +94,16 @@ Rails.application.routes.draw do resources :avoid_posting_versions, only: %i[index] + resources :staff_notes, except: %i[destroy] do + collection do + get :search + end + member do + put :delete + put :undelete + end + end + resources :tickets, except: %i[destroy] do member do post :claim @@ -294,7 +303,6 @@ Rails.application.routes.draw do resources :users do resource :password, :only => [:edit], :controller => "maintenance/user/passwords" resource :api_key, only: %i[show update destroy], controller: "maintenance/user/api_keys" - resources :staff_notes, only: [:index, :new, :create], controller: "admin/staff_notes" collection do get :home diff --git a/db/migrate/20240205174652_soft_deletable_staff_notes.rb b/db/migrate/20240205174652_soft_deletable_staff_notes.rb new file mode 100644 index 000000000..f5eb5e9e9 --- /dev/null +++ b/db/migrate/20240205174652_soft_deletable_staff_notes.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class SoftDeletableStaffNotes < ActiveRecord::Migration[7.1] + def change + add_column :staff_notes, :is_deleted, :boolean, null: false, default: false + add_reference :staff_notes, :updater, foreign_key: { to_table: :users }, null: false + remove_column :staff_notes, :resolved, :boolean, null: false, default: false + end +end diff --git a/db/structure.sql b/db/structure.sql index 3518c0174..511d11120 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1783,7 +1783,8 @@ CREATE TABLE public.staff_notes ( user_id bigint NOT NULL, creator_id integer NOT NULL, body character varying, - resolved boolean DEFAULT false NOT NULL + is_deleted boolean DEFAULT false NOT NULL, + updater_id bigint NOT NULL ); @@ -4230,6 +4231,13 @@ CREATE INDEX index_staff_audit_logs_on_user_id ON public.staff_audit_logs USING CREATE INDEX index_staff_notes_on_creator_id ON public.staff_notes USING btree (creator_id); +-- +-- Name: index_staff_notes_on_updater_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_staff_notes_on_updater_id ON public.staff_notes USING btree (updater_id); + + -- -- Name: index_staff_notes_on_user_id; Type: INDEX; Schema: public; Owner: - -- @@ -4671,6 +4679,14 @@ ALTER TABLE ONLY public.avoid_postings ADD CONSTRAINT fk_rails_d45cc0f1a1 FOREIGN KEY (creator_id) REFERENCES public.users(id); +-- +-- Name: staff_notes fk_rails_eaa7223eea; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.staff_notes + ADD CONSTRAINT fk_rails_eaa7223eea FOREIGN KEY (updater_id) REFERENCES public.users(id); + + -- -- PostgreSQL database dump complete -- @@ -4683,6 +4699,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240726170041'), ('20240709134926'), ('20240706061122'), +('20240205174652'), ('20240103002049'), ('20240103002040'), ('20240101042716'), diff --git a/test/functional/user_feedbacks_controller_test.rb b/test/functional/user_feedbacks_controller_test.rb index 0d6b1feac..b60e38725 100644 --- a/test/functional/user_feedbacks_controller_test.rb +++ b/test/functional/user_feedbacks_controller_test.rb @@ -93,7 +93,7 @@ class UserFeedbacksControllerTest < ActionDispatch::IntegrationTest end should "delete a feedback" do - assert_difference({ "UserFeedback.count" => -1, "ModAction.count" => 1 }) do + assert_difference({ "UserFeedback.count" => -1, "ModAction.count" => 2 }) do delete_auth user_feedback_path(@user_feedback), @critic end end @@ -101,7 +101,7 @@ class UserFeedbacksControllerTest < ActionDispatch::IntegrationTest context "by a moderator" do should "allow destroying feedbacks they created" do as(@mod) { @user_feedback = create(:user_feedback, user: @user) } - assert_difference({ "UserFeedback.count" => -1, "ModAction.count" => 1 }) do + assert_difference({ "UserFeedback.count" => -1, "ModAction.count" => 2 }) do delete_auth user_feedback_path(@user_feedback), @mod end end @@ -126,13 +126,13 @@ class UserFeedbacksControllerTest < ActionDispatch::IntegrationTest context "by an admin" do should "allow destroying feedbacks they created" do as(@admin) { @user_feedback = create(:user_feedback, user: @user) } - assert_difference({ "UserFeedback.count" => -1, "ModAction.count" => 1 }) do + assert_difference({ "UserFeedback.count" => -1, "ModAction.count" => 2 }) do delete_auth user_feedback_path(@user_feedback), @admin end end should "allow destroying feedbacks they did not create" do - assert_difference({ "UserFeedback.count" => -1, "ModAction.count" => 1 }) do + assert_difference({ "UserFeedback.count" => -1, "ModAction.count" => 2 }) do delete_auth user_feedback_path(@user_feedback, format: :json), @admin end end