api: refactor api attribute declarations.

Replace the `method_attributes` and `hidden_attributes` methods with
`api_attributes`. `api_attributes` can be used as a class macro:

    # include only the given attributes.
    api_attributes :id, :created_at, :creator_name, ...

    # include all default attributes plus the `creator_name` method.
    api_attributes including: [:creator_name]

or as an instance method:

    def api_attributes
       [:id, :created_at, :creator_name, ...]
    end

By default, all attributes are included except for IP addresses and
tsvector columns.
This commit is contained in:
evazion
2019-09-08 23:28:02 -05:00
parent 19f2cc1e74
commit d0f060d8eb
22 changed files with 98 additions and 209 deletions

View File

@@ -1,6 +1,7 @@
class ReportsController < ApplicationController
before_action :member_only, :except => [:upload_tags]
before_action :moderator_only, :only => [:post_versions, :post_versions_create, :down_voting_post_report, :down_voting_post_report_create]
respond_to :html, :xml, :json, only: [:upload_tags]
def uploads
@report = Reports::Uploads.new(params[:min_date], params[:max_date], params[:queries])
@@ -16,6 +17,7 @@ class ReportsController < ApplicationController
def upload_tags
@user = User.find(params[:user_id])
@upload_reports = Reports::UploadTags.includes(versions: { post: :versions }).for_user(params[:user_id]).order("id desc").paginate(params[:page], :limit => params[:limit])
respond_with(@upload_reports)
end
def down_voting_post

View File

@@ -17,7 +17,7 @@ class ApplicationResponder < ActionController::Responder
options[:root] ||= resource.table_name.dasherize if resource.respond_to?(:table_name)
end
options[:only] ||= params["only"].scan(/\w+/) if params["only"]
options[:only] ||= params["only"].scan(/\w+/).map(&:to_sym) if params["only"]
super
end

View File

@@ -1,32 +1,13 @@
module Reports
class UploadTags < ::Post
def readonly?
true
end
module ApiMethods
def as_json(options = {})
options ||= {}
options[:only] ||= [:id]
super(options)
end
def to_xml(options = {}, &block)
options ||= {}
options[:only] ||= [:id, :uploader_id]
super(options, &block)
end
def method_attributes
[:uploader_tags, :added_tags, :removed_tags]
end
def api_attributes
[:id, :uploader_id, :uploader_tags, :added_tags, :removed_tags]
end
include ApiMethods
def uploader_tags_array
@uploader_tags ||= begin
uploader_versions = versions.where(updater_id: uploader_id)

View File

@@ -215,35 +215,47 @@ class ApplicationRecord < ActiveRecord::Base
module ApiMethods
extend ActiveSupport::Concern
def serializable_hash(options = {})
options ||= {}
class_methods do
def api_attributes(*attributes, including: [])
return @api_attributes if @api_attributes
options[:include] ||= []
if attributes.present?
@api_attributes = attributes
else
@api_attributes = attribute_types.reject { |name, attr| attr.type.in?([:inet, :tsvector]) }.keys.map(&:to_sym)
end
options[:except] ||= []
options[:except] += hidden_attributes
options[:methods] ||= []
options[:methods] += method_attributes
if options[:only]
options[:methods] = options[:methods] & options[:only].map(&:to_sym)
options[:include] = options[:include] & options[:only].map(&:to_sym)
@api_attributes += including
@api_attributes
end
end
def api_attributes
self.class.api_attributes
end
def serializable_hash(options = {})
options[:only] ||= []
options[:include] ||= []
options[:methods] ||= []
attributes, methods = api_attributes.partition { |attr| has_attribute?(attr) }
methods += options[:methods]
includes = options[:include]
if options[:only].present?
attributes &= options[:only]
methods &= options[:only]
includes &= options[:only]
end
options[:only] = attributes
options[:methods] = methods
options[:include] = includes
hash = super(options)
hash.transform_keys { |key| key.delete("?") }
end
protected
def hidden_attributes
[:uploader_ip_addr, :updater_ip_addr, :creator_ip_addr]
end
def method_attributes
[]
end
end
concerning :ActiveRecordExtensions do

View File

@@ -22,6 +22,8 @@ class Comment < ApplicationRecord
:body => ->(user_name) {"@#{creator.name} mentioned you in a \"comment\":/posts/#{post_id}#comment-#{id} on post ##{post_id}:\n\n[quote]\n#{DText.excerpt(body, "@"+user_name)}\n[/quote]\n"},
)
api_attributes including: [:creator_name, :updater_name]
module SearchMethods
def deleted
where("comments.is_deleted = true")
@@ -145,14 +147,6 @@ class Comment < ApplicationRecord
select { |comment| comment.visibility(user) == :visible }
end
def hidden_attributes
super + [:body_index]
end
def method_attributes
super + [:creator_name, :updater_name]
end
def creator_name
creator.name
end

View File

@@ -18,6 +18,8 @@ class Dmail < ApplicationRecord
after_create :update_recipient
after_commit :send_email, on: :create
api_attributes including: [:key]
concerning :SpamMethods do
class_methods do
def is_spammer?(user)
@@ -103,16 +105,6 @@ class Dmail < ApplicationRecord
end
end
module ApiMethods
def hidden_attributes
super + [:message_index]
end
def method_attributes
super + [:key]
end
end
module SearchMethods
def sent_by(user)
where("dmails.from_id = ? AND dmails.owner_id != ?", user.id, user.id)
@@ -156,7 +148,6 @@ class Dmail < ApplicationRecord
include AddressMethods
include FactoryMethods
include ApiMethods
extend SearchMethods
def validate_sender_is_not_banned

View File

@@ -62,30 +62,7 @@ class ForumPost < ApplicationRecord
end
end
module ApiMethods
def as_json(options = {})
if CurrentUser.user.level < topic.min_level
options[:only] = [:id]
end
super(options)
end
def to_xml(options = {})
if CurrentUser.user.level < topic.min_level
options[:only] = [:id]
end
super(options)
end
def hidden_attributes
super + [:text_index]
end
end
extend SearchMethods
include ApiMethods
def self.new_reply(params)
if params[:topic_id]

View File

@@ -155,26 +155,6 @@ class ForumTopic < ApplicationRecord
(response_count / Danbooru.config.posts_per_page.to_f).ceil
end
def as_json(options = {})
if CurrentUser.user.level < min_level
options[:only] = [:id]
end
super(options)
end
def to_xml(options = {})
if CurrentUser.user.level < min_level
options[:only] = [:id]
end
super(options)
end
def hidden_attributes
super + [:text_index, :min_level]
end
def merge(topic)
ForumPost.where(:id => self.posts.map(&:id)).update_all(:topic_id => topic.id)
topic.update(response_count: topic.response_count + self.posts.length, updater_id: CurrentUser.id)

View File

@@ -2,6 +2,8 @@ class ModAction < ApplicationRecord
belongs_to :creator, :class_name => "User"
before_validation :initialize_creator, :on => :create
api_attributes including: [:category_id]
#####DIVISIONS#####
#Groups: 0-999
#Individual: 1000-1999
@@ -73,10 +75,6 @@ class ModAction < ApplicationRecord
self.class.categories[category]
end
def method_attributes
super + [:category_id]
end
def serializable_hash(*args)
super(*args).merge("description" => filtered_description)
end

View File

@@ -11,6 +11,8 @@ class Note < ApplicationRecord
after_save :create_version
validate :post_must_not_be_note_locked
api_attributes including: [:creator_name]
module SearchMethods
def active
where("is_active = TRUE")
@@ -26,22 +28,11 @@ class Note < ApplicationRecord
end
end
module ApiMethods
def hidden_attributes
super + [:body_index]
end
def method_attributes
super + [:creator_name]
end
end
def creator_name
creator.name
end
extend SearchMethods
include ApiMethods
def post_must_not_be_note_locked
if is_locked?

View File

@@ -17,6 +17,8 @@ class Pool < ApplicationRecord
after_save :create_version
after_create :synchronize!
api_attributes including: [:creator_name, :post_count]
module SearchMethods
def deleted
where("pools.is_deleted = true")
@@ -288,10 +290,6 @@ class Pool < ApplicationRecord
(post_count / CurrentUser.user.per_page.to_f).ceil
end
def method_attributes
super + [:creator_name, :post_count]
end
def creator_name
creator.name
end

View File

@@ -1457,23 +1457,13 @@ class Post < ApplicationRecord
end
module ApiMethods
def hidden_attributes
list = super + [:tag_index]
unless CurrentUser.is_moderator?
list += [:fav_string]
end
if !visible?
list += [:md5, :file_ext]
end
super + list
end
def method_attributes
list = super + [:uploader_name, :has_large, :has_visible_children, :children_ids, :is_favorited?] + TagCategory.categories.map {|x| "tag_string_#{x}".to_sym}
if visible?
list += [:file_url, :large_file_url, :preview_file_url]
end
list
def api_attributes
attributes = super
attributes += [:uploader_name, :has_large, :has_visible_children, :children_ids, :is_favorited?] + TagCategory.categories.map {|x| "tag_string_#{x}".to_sym}
attributes += [:file_url, :large_file_url, :preview_file_url] if visible?
attributes -= [:md5, :file_ext] if !visible?
attributes -= [:fav_string] if !CurrentUser.is_moderator?
attributes
end
def associated_attributes

View File

@@ -9,6 +9,8 @@ class PostAppeal < ApplicationRecord
before_validation :initialize_creator, :on => :create
validates_uniqueness_of :creator_id, :scope => :post_id, :message => "have already appealed this post"
api_attributes including: [:is_resolved]
module SearchMethods
def resolved
joins(:post).where("posts.is_deleted = false and posts.is_flagged = false")
@@ -64,8 +66,4 @@ class PostAppeal < ApplicationRecord
def appeal_count_for_creator
creator.post_appeals.recent.count
end
def method_attributes
super + [:is_resolved]
end
end

View File

@@ -260,7 +260,7 @@ class PostArchive < ApplicationRecord
updater.name
end
def method_attributes
def api_attributes
super + [:obsolete_added_tags, :obsolete_removed_tags, :unchanged_tags, :updater_name]
end

View File

@@ -94,22 +94,13 @@ class PostFlag < ApplicationRecord
end
end
module ApiMethods
def hidden_attributes
list = super
unless CurrentUser.can_view_flagger_on_post?(self)
list += [:creator_id]
end
super + list
end
def method_attributes
super + [:category]
end
def api_attributes
attributes = super + [:category]
attributes -= [:creator_id] unless CurrentUser.can_view_flagger_on_post?(self)
attributes
end
extend SearchMethods
include ApiMethods
def category
case reason

View File

@@ -72,6 +72,8 @@ class Upload < ApplicationRecord
scope :preprocessed, -> { where(status: "preprocessed") }
api_attributes including: [:uploader_name]
def initialize_attributes
self.uploader_id = CurrentUser.id
self.uploader_ip_addr = CurrentUser.ip_addr
@@ -227,18 +229,11 @@ class Upload < ApplicationRecord
end
end
module ApiMethods
def method_attributes
super + [:uploader_name]
end
end
include FileMethods
include StatusMethods
include UploaderMethods
include VideoMethods
extend SearchMethods
include ApiMethods
include SourceMethods
def uploader_is_not_limited

View File

@@ -567,33 +567,28 @@ class User < ApplicationRecord
end
module ApiMethods
# blacklist all attributes by default. whitelist only safe attributes.
def hidden_attributes
super + attributes.keys.map(&:to_sym)
end
def method_attributes
list = super + [
def api_attributes
attributes = [
:id, :created_at, :name, :inviter_id, :level, :base_upload_limit,
:post_upload_count, :post_update_count, :note_update_count,
:is_banned, :can_approve_posts, :can_upload_free, :is_super_voter,
:level_string,
:post_upload_count, :post_update_count, :note_update_count, :is_banned,
:can_approve_posts, :can_upload_free, :is_super_voter, :level_string,
]
if id == CurrentUser.user.id
list += BOOLEAN_ATTRIBUTES + [
attributes += BOOLEAN_ATTRIBUTES
attributes += [
:updated_at, :email, :last_logged_in_at, :last_forum_read_at,
:recent_tags, :comment_threshold, :default_image_size,
:favorite_tags, :blacklisted_tags, :time_zone, :per_page,
:custom_style, :favorite_count,
:api_regen_multiplier, :api_burst_limit, :remaining_api_limit,
:statement_timeout, :favorite_group_limit, :favorite_limit,
:tag_query_limit, :can_comment_vote?, :can_remove_from_pools?,
:is_comment_limited?, :can_comment?, :can_upload?, :max_saved_searches,
:custom_style, :favorite_count, :api_regen_multiplier,
:api_burst_limit, :remaining_api_limit, :statement_timeout,
:favorite_group_limit, :favorite_limit, :tag_query_limit,
:can_comment_vote?, :can_remove_from_pools?, :is_comment_limited?,
:can_comment?, :can_upload?, :max_saved_searches,
]
end
list
attributes
end
# extra attributes returned for /users/:id.json but not for /users.json.

View File

@@ -67,11 +67,9 @@ class UserNameChangeRequest < ApplicationRecord
end
end
def hidden_attributes
if CurrentUser.is_admin? || user == CurrentUser.user
[]
else
super + [:change_reason, :rejection_reason]
end
def api_attributes
attributes = super
attributes -= [:change_reason, :rejection_reason] unless CurrentUser.is_admin? || user == CurrentUser.user
attributes
end
end

View File

@@ -18,6 +18,8 @@ class WikiPage < ApplicationRecord
has_one :artist, -> {where(:is_active => true)}, :foreign_key => "name", :primary_key => "title"
has_many :versions, -> {order("wiki_page_versions.id ASC")}, :class_name => "WikiPageVersion", :dependent => :destroy
api_attributes including: [:creator_name, :category_name]
module SearchMethods
def titled(title)
where("title = ?", title.mb_chars.downcase.tr(" ", "_"))
@@ -88,22 +90,11 @@ class WikiPage < ApplicationRecord
end
end
module ApiMethods
def hidden_attributes
super + [:body_index]
end
def method_attributes
super + [:creator_name, :category_name]
end
end
def creator_name
creator.name
end
extend SearchMethods
include ApiMethods
def validate_not_locked
if is_locked? && !CurrentUser.is_builder?

View File

@@ -1,5 +0,0 @@
class Delayed::Job
def hidden_attributes
[:handler]
end
end

View File

@@ -217,6 +217,19 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
end
context "in api responses" do
should "not include restricted attributes" do
as(@user) { @post.update(tag_string: "loli") }
get_auth post_path(@post), @user, as: :json
assert_response :success
assert_nil(response.parsed_body["md5"])
assert_nil(response.parsed_body["file_url"])
assert_nil(response.parsed_body["fav_string"])
assert_equal(@post.uploader_name, response.parsed_body["uploader_name"])
end
end
end
context "update action" do

View File

@@ -84,11 +84,10 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
end
should "render the current users's profile in json" do
get_auth profile_path(format: :json), @user
get_auth profile_path, @user, as: :json
assert_response :success
json = as(@user) { @user.as_json(methods: @user.full_attributes + @user.method_attributes) }
assert_equal(json, response.parsed_body)
assert_equal(@user.upload_limit, response.parsed_body["upload_limit"])
end
should "redirect anonymous users to the sign in page" do