diff --git a/app/controllers/user_actions_controller.rb b/app/controllers/user_actions_controller.rb new file mode 100644 index 000000000..23ccaf514 --- /dev/null +++ b/app/controllers/user_actions_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class UserActionsController < ApplicationController + respond_to :html, :xml, :json + + def index + if user_id = params[:user_id] || params.dig(:search, :user_id) + @user = User.find(user_id) + elsif user_name = params.dig(:search, :user_name) + @user = User.find_by_name(user_name) + end + + @user_actions = authorize UserAction.for_user(CurrentUser.user).paginated_search(params, count_pages: @user.present?) + @user_actions = @user_actions.includes(:user, model: [:artist, :post, :note, :user, :creator, :banner, :bulk_update_request, :tag, :antecedent_tag, :consequent_tag, :model, :topic, :purchaser, :recipient, :forum_topic, forum_post: [:topic], comment: [:creator, :post]]) if request.format.html? + + respond_with(@user_actions) + end + + def show + @user_actions = authorize UserAction.find(params[:id]) + respond_with(@user_actions) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 423376eea..f3791026d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -199,7 +199,7 @@ module ApplicationHelper end def link_to_search(tag, **options) - link_to tag.name, posts_path(tags: tag.name), class: tag_class(tag), **options + link_to tag.pretty_name, posts_path(tags: tag.name), class: tag_class(tag), **options end def link_to_wiki(text, title = text, **options) diff --git a/app/models/application_record.rb b/app/models/application_record.rb index e6c268682..faca00400 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -234,6 +234,10 @@ class ApplicationRecord < ActiveRecord::Base end end + def revised? + updated_at > created_at + end + def warnings @warnings ||= ActiveModel::Errors.new(self) end diff --git a/app/models/ban.rb b/app/models/ban.rb index 115a9618c..c0cd8e31f 100644 --- a/app/models/ban.rb +++ b/app/models/ban.rb @@ -69,6 +69,10 @@ class Ban < ApplicationRecord ApplicationController.helpers.humanized_duration(duration) end + def forever? + duration.present? && duration >= 100.years + end + def expired? persisted? && expires_at < Time.zone.now end diff --git a/app/models/dmail.rb b/app/models/dmail.rb index 57aa89cac..b48afc2d2 100644 --- a/app/models/dmail.rb +++ b/app/models/dmail.rb @@ -76,7 +76,11 @@ class Dmail < ApplicationRecord module SearchMethods def visible(user) - where(owner: user) + if user.is_anonymous? + none + else + where(owner: user) + end end def sent_by(user) diff --git a/app/models/favorite.rb b/app/models/favorite.rb index 0c7a32219..24c721cbc 100644 --- a/app/models/favorite.rb +++ b/app/models/favorite.rb @@ -8,7 +8,7 @@ class Favorite < ApplicationRecord after_create :upvote_post_on_create after_destroy :unvote_post_on_destroy - scope :public_favorites, -> { where(user: User.has_public_favorites) } + scope :public_favorites, -> { where.not(user: User.has_private_favorites) } def self.visible(user) if user.is_admin? diff --git a/app/models/favorite_group.rb b/app/models/favorite_group.rb index 2e3430ec4..63ef60cc0 100644 --- a/app/models/favorite_group.rb +++ b/app/models/favorite_group.rb @@ -17,6 +17,9 @@ class FavoriteGroup < ApplicationRecord array_attribute :post_ids, parse: /\d+/, cast: :to_i + scope :is_public, -> { where(is_public: true) } + scope :is_private, -> { where(is_public: false) } + module SearchMethods def for_post(post_id) where_array_includes_any(:post_ids, [post_id]) @@ -29,7 +32,13 @@ class FavoriteGroup < ApplicationRecord end def visible(user) - where(is_public: true).or(where(creator_id: user.id)) + if user.is_owner? + all + elsif user.is_anonymous? + is_public + else + is_public.or(where(creator: user)) + end end def search(params) diff --git a/app/models/forum_post_vote.rb b/app/models/forum_post_vote.rb index 739b1d2cc..07230da7e 100644 --- a/app/models/forum_post_vote.rb +++ b/app/models/forum_post_vote.rb @@ -3,6 +3,8 @@ class ForumPostVote < ApplicationRecord belongs_to :creator, class_name: "User" belongs_to :forum_post + belongs_to :bulk_update_request, primary_key: :forum_post_id, foreign_key: :forum_post_id, optional: true + validates :creator_id, uniqueness: {scope: :forum_post_id} validates :score, inclusion: {in: [-1, 0, 1]} diff --git a/app/models/moderation_report.rb b/app/models/moderation_report.rb index ed2a7ad10..66301d6b5 100644 --- a/app/models/moderation_report.rb +++ b/app/models/moderation_report.rb @@ -35,8 +35,10 @@ class ModerationReport < ApplicationRecord def self.visible(user) if user.is_moderator? all - else + elsif !user.is_anonymous? where(creator: user) + else + none end end diff --git a/app/models/post_vote.rb b/app/models/post_vote.rb index 8ffb3f9f3..a9f4699a5 100644 --- a/app/models/post_vote.rb +++ b/app/models/post_vote.rb @@ -17,7 +17,7 @@ class PostVote < ApplicationRecord scope :positive, -> { where("post_votes.score > 0") } scope :negative, -> { where("post_votes.score < 0") } - scope :public_votes, -> { active.positive.where(user: User.has_public_favorites) } + scope :public_votes, -> { active.positive.where.not(user: User.has_private_favorites) } deletable diff --git a/app/models/saved_search.rb b/app/models/saved_search.rb index f0783777b..63d8af7b0 100644 --- a/app/models/saved_search.rb +++ b/app/models/saved_search.rb @@ -18,7 +18,11 @@ class SavedSearch < ApplicationRecord scope :has_tag, ->(name) { where_regex(:query, "(^| )[~-]?#{Regexp.escape(name)}( |$)", flags: "i") } def self.visible(user) - where(user: user) + if user.is_anonymous? + none + else + where(user: user) + end end concerning :Redis do diff --git a/app/models/tag_version.rb b/app/models/tag_version.rb index fbbbba6f1..262c3d4af 100644 --- a/app/models/tag_version.rb +++ b/app/models/tag_version.rb @@ -39,4 +39,8 @@ class TagVersion < ApplicationRecord def category_name TagCategory.reverse_mapping[category].capitalize end + + def pretty_name + name.tr("_", " ") + end end diff --git a/app/models/user_action.rb b/app/models/user_action.rb new file mode 100644 index 000000000..93b831fcd --- /dev/null +++ b/app/models/user_action.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +class UserAction < ApplicationRecord + belongs_to :model, polymorphic: true + belongs_to :user + + attribute :model_type, :string + attribute :model_id, :integer + attribute :user_id, :integer + attribute :event_type, :string + attribute :event_at, :time + + def self.model_types + %w[ArtistVersion ArtistCommentaryVersion Ban BulkUpdateRequest Comment + CommentVote Dmail FavoriteGroup ForumPost ForumPostVote ForumTopic + ModAction ModerationReport NoteVersion Post PostAppeal PostApproval + PostDisapproval PostFlag PostReplacement PostVote SavedSearch TagAlias + TagImplication TagVersion Upload User UserEvent UserFeedback UserUpgrade + UserNameChangeRequest WikiPageVersion] + end + + def self.for_user(user) + sql = <<~SQL.squish + (#{ArtistVersion.visible(user).select("'ArtistVersion'::character varying AS model_type, id AS model_id, updater_id AS user_id, 'create'::character varying AS event_type, created_at AS event_at").to_sql}) + UNION ALL + (#{ArtistCommentaryVersion.visible(user).select("'ArtistCommentaryVersion', id, updater_id, 'create', created_at").to_sql}) + UNION ALL + (#{Ban.visible(user).select("'Ban', id, user_id, 'subject', created_at").to_sql}) + UNION ALL + (#{BulkUpdateRequest.visible(user).select("'BulkUpdateRequest', id, user_id, 'create', created_at").to_sql}) + UNION ALL + (#{Comment.visible(user).select("'Comment', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{CommentVote.visible(user).select("'CommentVote', id, user_id, 'create', created_at").to_sql}) + UNION ALL + (#{Dmail.visible(user).sent.select("'Dmail', id, from_id, 'create', created_at").order(created_at: :desc).to_sql}) + UNION ALL + (#{FavoriteGroup.visible(user).select("'FavoriteGroup', id, creator_id, 'create', created_at").order(created_at: :desc).to_sql}) + UNION ALL + (#{ForumPost.visible(user).select("'ForumPost', id, creator_id, 'create', created_at").order(created_at: :desc).to_sql}) + UNION ALL + (#{ForumPostVote.visible(user).select("'ForumPostVote', id, creator_id, 'create', created_at").order(created_at: :desc).to_sql}) + UNION ALL + (#{ForumTopic.visible(user).select("'ForumTopic', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{ModAction.visible(user).select("'ModAction', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{ModerationReport.visible(user).select("'ModerationReport', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{NoteVersion.visible(user).select("'NoteVersion', id, updater_id, 'create', created_at").to_sql}) + UNION ALL + (#{Post.visible(user).select("'Post', id, uploader_id, 'create', created_at").to_sql}) + UNION ALL + (#{PostAppeal.visible(user).select("'PostAppeal', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{PostApproval.visible(user).select("'PostApproval', id, user_id, 'create', created_at").to_sql}) + UNION ALL + (#{PostDisapproval.visible(user).select("'PostDisapproval', id, user_id, 'create', created_at").to_sql}) + UNION ALL + (#{PostFlag.visible(user).select("'PostFlag', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{PostReplacement.visible(user).select("'PostReplacement', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{PostVote.visible(user).select("'PostVote', id, user_id, 'create', created_at").order(created_at: :desc).to_sql}) + UNION ALL + (#{SavedSearch.visible(user).select("'SavedSearch', id, user_id, 'create', created_at").order(created_at: :desc).to_sql}) + UNION ALL + (#{TagAlias.visible(user).select("'TagAlias', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{TagImplication.visible(user).select("'TagImplication', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{TagVersion.visible(user).select("'TagVersion', id, updater_id, 'create', created_at").where("updater_id IS NOT NULL").order(created_at: :desc).to_sql}) + UNION ALL + (#{Upload.visible(user).select("'Upload', id, uploader_id, 'create', created_at").order(created_at: :desc).to_sql}) + UNION ALL + (#{User.visible(user).select("'User', id, id, 'create', created_at").to_sql}) + UNION ALL + (#{UserEvent.visible(user).select("'UserEvent', id, user_id, 'create', created_at").to_sql}) + UNION ALL + (#{UserFeedback.visible(user).select("'UserFeedback', id, creator_id, 'create', created_at").to_sql}) + UNION ALL + (#{UserFeedback.visible(user).select("'UserFeedback', id, user_id, 'subject', created_at").to_sql}) + UNION ALL ( + (#{UserUpgrade.visible(user).select("'UserUpgrade', id, purchaser_id, 'create', created_at").where(status: [:complete, :refunded]).order(created_at: :desc).to_sql}) + ) UNION ALL + (#{UserNameChangeRequest.visible(user).select("'UserNameChangeRequest', id, user_id, 'create', created_at").to_sql}) + UNION ALL + (#{WikiPageVersion.visible(user).select("'WikiPageVersion', id, updater_id, 'create', created_at").to_sql}) + SQL + + from("(#{sql}) user_actions") + end + + def self.visible(user) + all + end + + def self.search(params) + q = search_attributes(params, :event_type, :user, :model) + + case params[:order] + when "event_at_asc" + q = q.order(event_at: :asc, model_id: :asc) + else + q = q.apply_default_order(params) + end + + q + end + + def self.default_order + order(event_at: :desc, model_id: :desc) + end + + def self.available_includes + [:user, :model] + end + + def readonly? + true + end +end diff --git a/app/policies/comment_policy.rb b/app/policies/comment_policy.rb index 0bcbd4c3e..20df232c1 100644 --- a/app/policies/comment_policy.rb +++ b/app/policies/comment_policy.rb @@ -21,6 +21,10 @@ class CommentPolicy < ApplicationPolicy user.is_moderator? end + def can_see_creator? + !record.is_deleted? || can_see_deleted? + end + def reply? !record.is_deleted? end diff --git a/app/policies/user_action_policy.rb b/app/policies/user_action_policy.rb new file mode 100644 index 000000000..bfb2bbc94 --- /dev/null +++ b/app/policies/user_action_policy.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class UserActionPolicy < ApplicationPolicy + def index? + user.is_moderator? + end + + def can_see_user? + case record.model_type + when "Comment" + policy(record.model).can_see_creator? + when "PostFlag" + policy(record.model).can_view_flagger? + when "PostDisapproval" + policy(record.model).can_view_creator? + else + true + end + end +end diff --git a/app/views/static/site_map.html.erb b/app/views/static/site_map.html.erb index a3f798f4b..1361113c8 100644 --- a/app/views/static/site_map.html.erb +++ b/app/views/static/site_map.html.erb @@ -145,6 +145,11 @@