diff --git a/app/logical/concerns/searchable.rb b/app/logical/concerns/searchable.rb index 02df65732..dac490044 100644 --- a/app/logical/concerns/searchable.rb +++ b/app/logical/concerns/searchable.rb @@ -125,6 +125,30 @@ module Searchable where_operator(qualified_column, *range) end + # @param attr [String] the name of the JSON field + # @param hash [Hash] the hash of values it should contain + def where_json_contains(attr, hash) + # XXX Hack to transform strings to numbers. Needed to match numeric JSON + # values when given string input values from an URL. + hash = hash.transform_values do |value| + if Integer(value, exception: false) + value.to_i + elsif Float(value, exception: false) + value.to_f + else + value + end + end + + where("#{qualified_column_for(attr)} @> :hash", hash: hash.to_json) + end + + # @param attr [String] the name of the JSON field + # @param hash [String] the key it should contain + def where_json_has_key(attr, key) + where("#{qualified_column_for(attr)} ? :key", key: key) + end + def search_boolean_attribute(attr, params) if params[attr].present? boolean_attribute_matches(attr, params[attr]) @@ -218,6 +242,8 @@ module Searchable search_inet_attribute(name, params) when :enum search_enum_attribute(name, params) + when :jsonb + search_jsonb_attribute(name, params) when :array search_array_attribute(name, subtype, params) else @@ -406,6 +432,26 @@ module Searchable relation end + def search_jsonb_attribute(name, params) + relation = all + + if params[name].present? + relation = relation.where_json_contains(:metadata, params[name]) + end + + if params["#{name}_has_key"] + relation = relation.where_json_has_key(:metadata, params["#{name}_has_key"]) + end + + if params["has_#{name}"].to_s.truthy? + relation = relation.where.not(name => "{}") + elsif params["has_#{name}"].to_s.falsy? + relation = relation.where(name => "{}") + end + + relation + end + def search_array_attribute(name, type, params) relation = all singular_name = name.to_s.singularize diff --git a/app/logical/upload_service/utils.rb b/app/logical/upload_service/utils.rb index e82fa1950..f2ec8f051 100644 --- a/app/logical/upload_service/utils.rb +++ b/app/logical/upload_service/utils.rb @@ -63,7 +63,7 @@ class UploadService process_resizes(upload, file, original_post_id) - MediaAsset.create_from_media_file!(media_file) + MediaAsset.create!(file: media_file) end def automatic_tags(media_file) diff --git a/app/models/media_asset.rb b/app/models/media_asset.rb index 6a2d8d1ae..8a01b1393 100644 --- a/app/models/media_asset.rb +++ b/app/models/media_asset.rb @@ -1,20 +1,20 @@ class MediaAsset < ApplicationRecord has_one :media_metadata, dependent: :destroy - def self.create_from_media_file!(media_file) - create!( - md5: media_file.md5, - file_ext: media_file.file_ext, - file_size: media_file.file_size, - image_width: media_file.width, - image_height: media_file.height, - media_metadata: MediaMetadata.new(metadata: media_file.metadata), - ) - end - def self.search(params) q = search_attributes(params, :id, :created_at, :updated_at, :md5, :file_ext, :file_size, :image_width, :image_height) q = q.apply_default_order(params) q end + + def file=(file_or_path) + media_file = file_or_path.is_a?(MediaFile) ? file_or_path : MediaFile.open(file_or_path) + + self.md5 = media_file.md5 + self.file_ext = media_file.file_ext + self.file_size = media_file.file_size + self.image_width = media_file.width + self.image_height = media_file.height + self.media_metadata = MediaMetadata.new(file: media_file) + end end diff --git a/app/models/media_metadata.rb b/app/models/media_metadata.rb index d22c4e24e..ce7e394a7 100644 --- a/app/models/media_metadata.rb +++ b/app/models/media_metadata.rb @@ -15,8 +15,12 @@ class MediaMetadata < ApplicationRecord belongs_to :media_asset def self.search(params) - q = search_attributes(params, :id, :created_at, :updated_at, :media_asset_id) + q = search_attributes(params, :id, :created_at, :updated_at, :media_asset, :metadata) q = q.apply_default_order(params) q end + + def file=(file_or_path) + self.metadata = MediaFile.open(file_or_path).metadata + end end diff --git a/test/functional/media_metadata_controller_test.rb b/test/functional/media_metadata_controller_test.rb index 1fb9e9313..7f78c5064 100644 --- a/test/functional/media_metadata_controller_test.rb +++ b/test/functional/media_metadata_controller_test.rb @@ -9,6 +9,26 @@ class MediaMetadataControllerTest < ActionDispatch::IntegrationTest assert_response :success end + + context "searching" do + setup do + @jpg = create(:media_metadata, file: "test/files/test.jpg") + @gif = create(:media_metadata, file: "test/files/test.gif") + @png = create(:media_metadata, file: "test/files/test.png") + end + + should respond_to_search(has_metadata: true).with { [@png, @gif, @jpg] } + + should respond_to_search(metadata_has_key: "File:ColorComponents").with { [@jpg] } + should respond_to_search(metadata: { "File:ColorComponents": 3 }).with { [@jpg] } + should respond_to_search(metadata: { "File:ColorComponents": "3" }).with { [@jpg] } + + should respond_to_search(metadata_has_key: "GIF:GIFVersion").with { [@gif] } + should respond_to_search(metadata: { "GIF:GIFVersion": "89a" }).with { [@gif] } + + should respond_to_search(metadata_has_key: "PNG:ColorType").with { [@png] } + should respond_to_search(metadata: { "PNG:ColorType": "RGB" }).with { [@png] } + end end end end