[StaffNotes] Add updating and deleting (#823)

This commit is contained in:
clragon 2025-01-13 04:28:32 +01:00 committed by GitHub
parent afce6b614c
commit 29b98e5002
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 387 additions and 129 deletions

View File

@ -156,3 +156,6 @@ Style/TrailingCommaInArrayLiteral:
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: consistent_comma
Rails/NotNullColumn:
Enabled: false

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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';

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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,9 +95,20 @@ 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)
module SearchMethods
def visible(user)
if user.is_staff?
all
else
where.not(action: ProtectedActionKeys)
end
end
def search(params)
q = super
q = q.where_user(:creator_id, :creator, params)
@ -104,6 +119,15 @@ class ModAction < ApplicationRecord
q.apply_basic_order(params)
end
end
def can_view?(user)
if user.is_staff?
true
else
ProtectedActionKeys.exclude?(action)
end
end
def values
original_values = self[: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

View File

@ -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

View File

@ -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

View File

@ -1,13 +0,0 @@
<% if CurrentUser.can_view_staff_notes? %>
<div class="staff-notes-section styled-dtext">
<details>
<summary>Staff Notes (<%= user.staff_notes.count %>)</summary>
<div>
<h4><%= link_to "Staff Notes", user_staff_notes_path(user_id: user.id) %></h4>
<%= render "admin/staff_notes/partials/list_of_notes", staff_notes: user.staff_notes.limit(15), show_receiver_name: false %>
<h4>New Staff Note</h4>
<%= render "admin/staff_notes/partials/new", user: user, staff_note: StaffNote.new(user_id: user.id) %>
</div>
</details>
</div>
<% end %>

View File

@ -1,3 +0,0 @@
<div id="p-staff-notes-list" class="staff-note-list">
<%= render partial: "admin/staff_notes/partials/staff_note", collection: staff_notes, locals: { show_receiver_name: show_receiver_name } %>
</div>

View File

@ -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 %>

View File

@ -1,19 +0,0 @@
<article class="staff-note comment-post-grid" id="staff-note-<%= staff_note.id %>">
<div class="author-info">
<div class="name-rank">
<h4 class="author-name"><%= link_to_user staff_note.creator %></h4>
<%= staff_note.creator.level_string %>
</div>
<div class="post-time">
<%= 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}") %>
</div>
</div>
<div class="content">
<% if show_receiver_name %>
<h4>On <%= link_to_user staff_note.user %></h4>
<% end %>
<div class="body dtext-container">
<%= format_text(staff_note.body, allow_color: true) %>
</div>
</div>
</article>

View File

@ -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 %>

View File

@ -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 %>

View File

@ -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 %>

View File

@ -0,0 +1,13 @@
<div id="c-staff-notes">
<div id="a-edit">
<h1>Edit Staff Note For <%= link_to_user @staff_note.user %></h1>
<%= render "staff_notes/partials/edit", staff_note: @staff_note %>
</div>
</div>
<%= render "secondary_links" %>
<% content_for(:page_title) do %>
Edit Staff Note
<% end %>

View File

@ -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) %>
</div>
</div>
<%= render "users/secondary_links" %>
<%= render "secondary_links" %>
<% content_for(:page_title) do %>
<%= @user ? "#{@user.name} - Staff Notes" : "Staff Notes" %>

View File

@ -2,11 +2,11 @@
<div id="a-new">
<h1>New Staff Note for <%= link_to_user(@user) %></h1>
<%= render "admin/staff_notes/partials/new", staff_note: @staff_note, user: @user %>
<%= render "staff_notes/partials/new", staff_note: @staff_note, user: @user %>
</div>
</div>
<%= render "users/secondary_links" %>
<%= render "secondary_links" %>
<% content_for(:page_title) do %>
New Staff Note

View File

@ -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 %>

View File

@ -0,0 +1,15 @@
<% if CurrentUser.can_view_staff_notes? %>
<div class="staff-notes-section styled-dtext">
<details>
<summary>Staff Notes (<%= user.staff_notes.count %>)</summary>
<div>
<h4><%= link_to "Staff Notes", staff_notes_path(search: { user_id: user.id }) %></h4>
<%= render "staff_notes/partials/list_of_notes", staff_notes: user.staff_notes.limit(15), show_receiver_name: false %>
<div class="new-staff-note">
<p><%= link_to "Create »", new_staff_note_path(search: { user_id: user.id }), class: "expand-new-staff-note" %></p>
<%= render "staff_notes/partials/new", user: user, staff_note: StaffNote.new(user_id: user.id), hidden: true %>
</div>
</div>
</details>
</div>
<% end %>

View File

@ -0,0 +1,3 @@
<div id="p-staff-notes-list" class="staff-note-list">
<%= render partial: "staff_notes/partials/staff_note", collection: staff_notes, locals: { show_receiver_name: show_receiver_name } %>
</div>

View File

@ -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 %>

View File

@ -0,0 +1,39 @@
<article class="staff-note comment-post-grid" data-is-deleted="<%= staff_note.is_deleted %>" id="staff-note-<%= staff_note.id %>">
<div class="author-info">
<div class="name-rank">
<h4 class="author-name"><%= link_to_user staff_note.creator %></h4>
<%= staff_note.creator.level_string %>
</div>
<div class="post-time">
<%= 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}") %>
</div>
</div>
<div class="content">
<% if show_receiver_name %>
<h4>On <%= link_to_user staff_note.user %></h4>
<% end %>
<div class="body dtext-container">
<%= format_text(staff_note.body, allow_color: true) %>
</div>
<%= render "application/update_notice", record: staff_note %>
<div class="content-menu">
<menu>
<% if staff_note.can_edit?(CurrentUser.user) %>
<li><%= link_to "Edit", edit_staff_note_path(staff_note), id: "edit-staff-note-link-#{staff_note.id}", class: "edit-staff-note-link" %></li>
<% end %>
<% if staff_note.can_delete?(CurrentUser.user) %>
<% if staff_note.is_deleted? %>
<li><%= link_to "Undelete", undelete_staff_note_path(staff_note), method: :put %></li>
<% else %>
<li><%= link_to "Delete", delete_staff_note_path(staff_note), method: :put %></li>
<% end %>
<% end %>
</menu>
</div>
<% if staff_note.can_edit?(CurrentUser.user) %>
<%= render "staff_notes/partials/edit", staff_note: staff_note, hidden: true %>
<% end %>
</div>
</article>

View File

@ -0,0 +1,13 @@
<div id="c-staff-notes">
<div id="a-search">
<h1>Search Staff Notes</h1>
<%= render "search" %>
</div>
</div>
<%= render "secondary_links" %>
<% content_for(:page_title) do %>
Search Staff Notes
<% end %>

View File

@ -0,0 +1,13 @@
<div id="c-staff-notes">
<div id="a-show">
<h1>Staff Note For <%= link_to_user @staff_note.user %></h1>
<%= render "staff_notes/partials/staff_note", staff_note: @staff_note, show_receiver_name: true %>
</div>
</div>
<%= render "secondary_links" %>
<% content_for(:page_title) do %>
Staff Note - <%= @staff_note.user_name %>
<% end %>

View File

@ -119,6 +119,9 @@
<li><%= link_to("Stuck DNP tags", new_admin_stuck_dnp_path) %></li>
<li><%= link_to("Destroyed Posts", admin_destroyed_posts_path) %></li>
<% end %>
<% if CurrentUser.is_staff? %>
<li><%= link_to("Staff Notes", staff_notes_path) %></li>
<% end %>
<li><%= link_to("Upload Whitelist", upload_whitelists_path) %></li>
<li><%= link_to("Mod Actions", mod_actions_path) %></li>
<li><%= link_to("Bulk Update Requests", bulk_update_requests_path) %></li>

View File

@ -1,7 +1,7 @@
<div id="c-users">
<div id="a-show">
<%= 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 %>

View File

@ -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

View File

@ -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

View File

@ -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'),

View File

@ -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