diff --git a/app/controllers/discourse_user_notes/user_notes_controller.rb b/app/controllers/discourse_user_notes/user_notes_controller.rb new file mode 100644 index 0000000..c6d1e37 --- /dev/null +++ b/app/controllers/discourse_user_notes/user_notes_controller.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module DiscourseUserNotes + class UserNotesController < ::ApplicationController + requires_plugin DiscourseUserNotes::PLUGIN_NAME + before_action :ensure_logged_in + before_action :ensure_staff + + def index + user = User.where(id: params[:user_id]).first + raise Discourse::NotFound if user.blank? + + notes = ::DiscourseUserNotes.notes_for(params[:user_id]) + render json: { extras: { username: user.username }, user_notes: create_json(notes.reverse) } + end + + def create + user = User.where(id: params[:user_note][:user_id]).first + raise Discourse::NotFound if user.blank? + extras = {} + if post_id = params[:user_note][:post_id] + extras[:post_id] = post_id + end + + user_note = + ::DiscourseUserNotes.add_note(user, params[:user_note][:raw], current_user.id, extras) + + render json: create_json(user_note) + end + + def destroy + user = User.where(id: params[:user_id]).first + raise Discourse::NotFound if user.blank? + + raise Discourse::InvalidAccess.new unless guardian.can_delete_user_notes? + + ::DiscourseUserNotes.remove_note(user, params[:id]) + render json: success_json + end + + protected + + def create_json(obj) + # Avoid n+1 + if obj.is_a?(Array) + users_by_id = {} + posts_by_id = {} + User.where(id: obj.map { |o| o[:created_by] }).each { |u| users_by_id[u.id] = u } + Post.with_deleted.where(id: obj.map { |o| o[:post_id] }).each { |p| posts_by_id[p.id] = p } + obj.each do |o| + o[:created_by] = users_by_id[o[:created_by].to_i] + o[:post] = posts_by_id[o[:post_id].to_i] + end + else + obj[:created_by] = User.where(id: obj[:created_by]).first + obj[:post] = Post.with_deleted.where(id: obj[:post_id]).first + end + + serialize_data(obj, ::UserNoteSerializer) + end + end +end diff --git a/app/serializers/user_note_serializer.rb b/app/serializers/user_note_serializer.rb new file mode 100644 index 0000000..a578ad7 --- /dev/null +++ b/app/serializers/user_note_serializer.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +class ::UserNoteSerializer < ApplicationSerializer + attributes( + :id, + :user_id, + :raw, + :created_by, + :created_at, + :can_delete, + :post_id, + :post_url, + :post_title, + ) + + def id + object[:id] + end + + def user_id + object[:user_id] + end + + def raw + object[:raw] + end + + def created_by + BasicUserSerializer.new(object[:created_by], scope: scope, root: false) + end + + def created_at + object[:created_at] + end + + def can_delete + scope.can_delete_user_notes? + end + + def post_id + object[:post_id] + end + + def post_url + url = object[:post].try(:url) + + # In case the topic is deleted + url = "/t/#{object[:post].topic_id}/#{object[:post].post_number}" if url == "/404" + + "#{Discourse.base_path}#{url}" + end + + def post_title + object[:post].try(:title) + end + + def topic_id + object[:topic_id] + end +end diff --git a/plugin.rb b/plugin.rb index 58a194e..0192d21 100644 --- a/plugin.rb +++ b/plugin.rb @@ -11,16 +11,17 @@ enabled_site_setting :user_notes_enabled register_asset "stylesheets/user_notes.scss" -register_svg_icon "sticky-note" if respond_to?(:register_svg_icon) - -COUNT_FIELD = "user_notes_count" +register_svg_icon "sticky-note" after_initialize do require_dependency "user" module ::DiscourseUserNotes + PLUGIN_NAME = "discourse_user_notes" + COUNT_FIELD = "user_notes_count" + class Engine < ::Rails::Engine - engine_name "discourse_user_notes" + engine_name PLUGIN_NAME isolate_namespace DiscourseUserNotes end @@ -47,7 +48,7 @@ after_initialize do notes << record ::PluginStore.set("user_notes", key_for(user.id), notes) - user.custom_fields[COUNT_FIELD] = notes.size + user.custom_fields[DiscourseUserNotes::COUNT_FIELD] = notes.size user.save_custom_fields record @@ -62,136 +63,23 @@ after_initialize do else ::PluginStore.remove("user_notes", key_for(user.id)) end - user.custom_fields[COUNT_FIELD] = notes.size + user.custom_fields[DiscourseUserNotes::COUNT_FIELD] = notes.size user.save_custom_fields end end - require_dependency "application_serializer" - class ::UserNoteSerializer < ApplicationSerializer - attributes( - :id, - :user_id, - :raw, - :created_by, - :created_at, - :can_delete, - :post_id, - :post_url, - :post_title, - ) + require_relative "app/serializers/user_note_serializer.rb" + require_relative "app/controllers/discourse_user_notes/user_notes_controller.rb" - def id - object[:id] - end + Discourse::Application.routes.append { mount ::DiscourseUserNotes::Engine, at: "/user_notes" } - def user_id - object[:user_id] - end - - def raw - object[:raw] - end - - def created_by - BasicUserSerializer.new(object[:created_by], scope: scope, root: false) - end - - def created_at - object[:created_at] - end - - def can_delete - scope.can_delete_user_notes? - end - - def post_id - object[:post_id] - end - - def post_url - url = object[:post].try(:url) - - # In case the topic is deleted - url = "/t/#{object[:post].topic_id}/#{object[:post].post_number}" if url == "/404" - - "#{Discourse.base_path}#{url}" - end - - def post_title - object[:post].try(:title) - end - - def topic_id - object[:topic_id] - end + DiscourseUserNotes::Engine.routes.draw do + get "/" => "user_notes#index" + post "/" => "user_notes#create" + delete "/:id" => "user_notes#destroy" end - require_dependency "application_controller" - class DiscourseUserNotes::UserNotesController < ::ApplicationController - before_action :ensure_logged_in - before_action :ensure_staff - - def index - user = User.where(id: params[:user_id]).first - raise Discourse::NotFound if user.blank? - - notes = ::DiscourseUserNotes.notes_for(params[:user_id]) - render json: { extras: { username: user.username }, user_notes: create_json(notes.reverse) } - end - - def create - user = User.where(id: params[:user_note][:user_id]).first - raise Discourse::NotFound if user.blank? - extras = {} - if post_id = params[:user_note][:post_id] - extras[:post_id] = post_id - end - - user_note = - ::DiscourseUserNotes.add_note(user, params[:user_note][:raw], current_user.id, extras) - - render json: create_json(user_note) - end - - def destroy - user = User.where(id: params[:user_id]).first - raise Discourse::NotFound if user.blank? - - raise Discourse::InvalidAccess.new unless guardian.can_delete_user_notes? - - ::DiscourseUserNotes.remove_note(user, params[:id]) - render json: success_json - end - - protected - - def create_json(obj) - # Avoid n+1 - if obj.is_a?(Array) - users_by_id = {} - posts_by_id = {} - User.where(id: obj.map { |o| o[:created_by] }).each { |u| users_by_id[u.id] = u } - Post.with_deleted.where(id: obj.map { |o| o[:post_id] }).each { |p| posts_by_id[p.id] = p } - obj.each do |o| - o[:created_by] = users_by_id[o[:created_by].to_i] - o[:post] = posts_by_id[o[:post_id].to_i] - end - else - obj[:created_by] = User.where(id: obj[:created_by]).first - obj[:post] = Post.with_deleted.where(id: obj[:post_id]).first - end - - serialize_data(obj, ::UserNoteSerializer) - end - end - - # TODO Drop after Discourse 2.6.0 release - if respond_to?(:allow_staff_user_custom_field) - allow_staff_user_custom_field(COUNT_FIELD) - else - whitelist_staff_user_custom_field(COUNT_FIELD) - end + allow_staff_user_custom_field(DiscourseUserNotes::COUNT_FIELD) add_to_class(Guardian, :can_delete_user_notes?) do (SiteSetting.user_notes_moderators_delete? && user.staff?) || user.admin? @@ -201,14 +89,6 @@ after_initialize do object.custom_fields && object.custom_fields["user_notes_count"].to_i end - DiscourseUserNotes::Engine.routes.draw do - get "/" => "user_notes#index" - post "/" => "user_notes#create" - delete "/:id" => "user_notes#destroy" - end - - Discourse::Application.routes.append { mount ::DiscourseUserNotes::Engine, at: "/user_notes" } - add_model_callback(UserWarning, :after_commit, on: :create) do user = User.find_by_id(self.user_id) created_by_user = User.find_by_id(self.created_by_id) @@ -261,6 +141,7 @@ after_initialize do reason: details[:reason], ) end + note_args = {} if post = Post.with_deleted.where(id: details[:post_id]).first note_args = { post_id: post.id, topic_id: post.topic_id } @@ -269,68 +150,66 @@ after_initialize do ::DiscourseUserNotes.add_note(details[:user], raw_note, Discourse::SYSTEM_USER_ID, note_args) end - if respond_to? :add_report - add_report("user_notes") do |report| - report.modes = [:table] + add_report("user_notes") do |report| + report.modes = [:table] - report.data = [] + report.data = [] - report.labels = [ - { - type: :user, - properties: { - username: :username, - id: :user_id, - avatar: :user_avatar_template, - }, - title: I18n.t("reports.user_notes.labels.user"), + report.labels = [ + { + type: :user, + properties: { + username: :username, + id: :user_id, + avatar: :user_avatar_template, }, - { - type: :user, - properties: { - username: :moderator_username, - id: :moderator_id, - avatar: :moderator_avatar_template, - }, - title: I18n.t("reports.user_notes.labels.moderator"), + title: I18n.t("reports.user_notes.labels.user"), + }, + { + type: :user, + properties: { + username: :moderator_username, + id: :moderator_id, + avatar: :moderator_avatar_template, }, - { type: :text, property: :note, title: I18n.t("reports.user_notes.labels.note") }, - ] + title: I18n.t("reports.user_notes.labels.moderator"), + }, + { type: :text, property: :note, title: I18n.t("reports.user_notes.labels.note") }, + ] - values = [] - values = - PluginStoreRow - .where(plugin_name: "user_notes") - .where("value::json->0->>'created_at'>=?", report.start_date) - .where("value::json->0->>'created_at'<=?", report.end_date) - .pluck(:value) + values = [] + values = + PluginStoreRow + .where(plugin_name: "user_notes") + .where("value::json->0->>'created_at'>=?", report.start_date) + .where("value::json->0->>'created_at'<=?", report.end_date) + .pluck(:value) - values.each do |value| - notes = JSON.parse(value) - notes.each do |note| - data = {} - created_at = Time.parse(note["created_at"]) - user = User.find_by(id: note["user_id"]) - moderator = User.find_by(id: note["created_by"]) + values.each do |value| + notes = JSON.parse(value) + notes.each do |note| + data = {} + created_at = Time.parse(note["created_at"]) + user = User.find_by(id: note["user_id"]) + moderator = User.find_by(id: note["created_by"]) - if user && moderator - data[:created_at] = created_at - data[:user_id] = user.id - data[:username] = user.username_lower - data[:user_avatar_template] = User.avatar_template( - user.username_lower, - user.uploaded_avatar_id, - ) - data[:moderator_id] = moderator.id - data[:moderator_username] = moderator.username_lower - data[:moderator_avatar_template] = User.avatar_template( - moderator.username_lower, - moderator.uploaded_avatar_id, - ) - data[:note] = note["raw"] + if user && moderator + data[:created_at] = created_at + data[:user_id] = user.id + data[:username] = user.username_lower + data[:user_avatar_template] = User.avatar_template( + user.username_lower, + user.uploaded_avatar_id, + ) + data[:moderator_id] = moderator.id + data[:moderator_username] = moderator.username_lower + data[:moderator_avatar_template] = User.avatar_template( + moderator.username_lower, + moderator.uploaded_avatar_id, + ) + data[:note] = note["raw"] - report.data << data - end + report.data << data end end end