cloudinary 1.18.1 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42964b1e7b2a93986d13479b494110e502f4d1466f179410e22f58995d75ee1f
4
- data.tar.gz: 80eae5018288bedfa0319537850b3e173142d437cf016c83ee3b8ba1f412c5d5
3
+ metadata.gz: 635b7d9336fcde0655fd8b348bda1cdf443a60e123991a1d6197a5a55e9d159b
4
+ data.tar.gz: 8cef3ea547da816bf1125243e04ed72834414f8529a27579e2ba3916571d79bc
5
5
  SHA512:
6
- metadata.gz: 7021d7cd3d1d828660c68d803edc312dc1b7a7a9ac56cb0d9cc6dee894fa47fbb18950f70eb54e1b3e0a20a3f392632f1ef0ac5786f70ded929b2d912f20f3fa
7
- data.tar.gz: 5c67f41050189259c3442500525bb27d54355a8b510b4e5f9f32535a8fa0b2dcfc9fb1102403afbbe3fc88e3c94c91b42fe6028d64dab48d6cc86062c8bd1061
6
+ metadata.gz: a6852e75360ff11e51b3d6be2e8e15f48b06d39ac17b189b6fedd54f5a3041725514db2a799c8d08db3af6f355c9ae21559b3d69015ad8eea54e701554ec3d40
7
+ data.tar.gz: 32997f5c779e28bb3711fea013d0131b922c92cc4e7314ec37d6c6ca45e8e2e765cfaf2979a3f04f7e4b57a5ddd9dc5ddb0b4c2b0afea9c3ff65e428a6005f41
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ 1.19.0 / 2021-03-05
2
+ ==================
3
+
4
+ New functionality and features
5
+ ------------------------------
6
+
7
+ * Add Account Provisioning API
8
+ * Add support for `api_proxy` parameter
9
+ * Add support for `date` parameter in `usage` Admin API
10
+
11
+ Other Changes
12
+ -------------
13
+
14
+ * Fix direct upload of raw files
15
+ * Improve unit testing of add-ons
16
+ * Change test for `eval` upload parameter
17
+ * Bump vulnerable version of rubyzip
18
+ * Fix `cloudinary.gemspec` glob issue
19
+
1
20
 
2
21
  1.18.1 / 2020-09-30
3
22
  ===================
data/cloudinary.gemspec CHANGED
@@ -15,12 +15,17 @@ Gem::Specification.new do |s|
15
15
 
16
16
  s.rubyforge_project = "cloudinary"
17
17
 
18
- s.files = (`git ls-files`.split("\n") - `git ls-files {test,spec,features,samples}/*`.split("\n")) + Dir.glob("vendor/assets/javascripts/*/*") + Dir.glob("vendor/assets/html/*")
18
+ s.files = `git ls-files`.split("\n").select { |f| !f.start_with?("test", "spec", "features", "samples") } + Dir.glob("vendor/assets/javascripts/*/*") + Dir.glob("vendor/assets/html/*")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
22
  s.add_dependency "aws_cf_signer"
23
- s.add_dependency "rest-client"
23
+
24
+ if RUBY_VERSION >= "2.0.0"
25
+ s.add_dependency "rest-client", ">= 2.0.0"
26
+ else
27
+ s.add_dependency "rest-client"
28
+ end
24
29
 
25
30
  s.add_development_dependency "actionpack"
26
31
  s.add_development_dependency "nokogiri"
@@ -39,7 +44,7 @@ Gem::Specification.new do |s|
39
44
  s.add_development_dependency "railties", "<= 4.2.7" if RUBY_VERSION <= "1.9.3"
40
45
  s.add_development_dependency "rspec-rails"
41
46
 
42
- s.add_development_dependency "rubyzip", "<=1.2.0" # support testing Ruby 1.9
47
+ s.add_development_dependency "rubyzip"
43
48
 
44
49
  if RUBY_VERSION <= "2.4.0"
45
50
  s.add_development_dependency "simplecov", "<= 0.17.1" # support testing Ruby 1.9
@@ -72,6 +72,14 @@ module ActiveStorage
72
72
  instrument :url, key: key do |payload|
73
73
  options = {:resource_type => resource_type(nil, key)}.merge(@options.merge(options.symbolize_keys))
74
74
  options[:public_id] = public_id_internal(key)
75
+ # Provide file format for raw files, since js client does not include original file name.
76
+ #
77
+ # When the file is uploaded from the server, the request includes original filename. That allows Cloudinary
78
+ # to identify file extension and append it to the public id of the file (raw files include file extension
79
+ # in their public id, opposed to transformable assets (images/video) that use only basename). When uploading
80
+ # through direct upload (client side js), filename is missing, and that leads to inconsistent/broken URLs.
81
+ # To avoid that, we explicitly pass file format in options.
82
+ options[:format] = ext_for_file(key) if options[:resource_type] == "raw"
75
83
  options[:context] = {active_storage_key: key}
76
84
  options.delete(:file)
77
85
  payload[:url] = api_uri("upload", options)
@@ -174,7 +182,7 @@ module ActiveStorage
174
182
  # @param [string] content_type The content type of the file.
175
183
  #
176
184
  # @return [string] The extension of the filename.
177
- def ext_for_file(key, filename, content_type)
185
+ def ext_for_file(key, filename = nil, content_type = nil)
178
186
  if filename.blank?
179
187
  options = key.respond_to?(:attributes) ? key.attributes : {}
180
188
  filename = ActiveStorage::Filename.new(options[:filename]) if options.has_key?(:filename)
@@ -200,6 +208,8 @@ module ActiveStorage
200
208
  end
201
209
 
202
210
  def content_type_to_resource_type(content_type)
211
+ return 'image' if content_type.nil?
212
+
203
213
  type, subtype = content_type.split('/')
204
214
  case type
205
215
  when 'video', 'audio'
data/lib/cloudinary.rb CHANGED
@@ -16,7 +16,12 @@ require "cloudinary/missing"
16
16
  module Cloudinary
17
17
  autoload :Utils, 'cloudinary/utils'
18
18
  autoload :Uploader, 'cloudinary/uploader'
19
+ autoload :BaseConfig, "cloudinary/base_config"
20
+ autoload :Config, "cloudinary/config"
21
+ autoload :AccountConfig, "cloudinary/account_config"
22
+ autoload :BaseApi, "cloudinary/base_api"
19
23
  autoload :Api, "cloudinary/api"
24
+ autoload :AccountApi, "cloudinary/account_api"
20
25
  autoload :Downloader, "cloudinary/downloader"
21
26
  autoload :Blob, "cloudinary/blob"
22
27
  autoload :PreloadedFile, "cloudinary/preloaded_file"
@@ -58,64 +63,42 @@ module Cloudinary
58
63
  "ept" => "eps"
59
64
  }
60
65
 
61
- @@config = nil
62
-
66
+ # Cloudinary config
67
+ #
68
+ # @param [Hash] new_config If +new_config+ is passed, Config will be updated with it
69
+ # @yieldparam [OpenStruct] Config can be updated in the block
70
+ #
71
+ # @return [OpenStruct]
63
72
  def self.config(new_config=nil)
64
- first_time = @@config.nil?
65
- @@config ||= OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {}))
66
-
67
- config_from_env if first_time
73
+ @@config ||= make_new_config(Config)
68
74
 
69
- set_config(new_config) if new_config
70
- yield(@@config) if block_given?
75
+ @@config.update(new_config) if new_config
76
+ yield @@config if block_given?
71
77
 
72
78
  @@config
73
79
  end
74
80
 
75
- def self.config_from_url(url)
76
- @@config ||= OpenStruct.new
77
- return unless url && !url.empty?
78
- uri = URI.parse(url)
79
- if !uri.scheme || "cloudinary" != uri.scheme.downcase
80
- raise(CloudinaryException,
81
- "Invalid CLOUDINARY_URL scheme. Expecting to start with 'cloudinary://'")
82
- end
83
- set_config(
84
- "cloud_name" => uri.host,
85
- "api_key" => uri.user,
86
- "api_secret" => uri.password,
87
- "private_cdn" => !uri.path.blank?,
88
- "secure_distribution" => uri.path[1..-1]
89
- )
90
- uri.query.to_s.split("&").each do
91
- |param|
92
- key, value = param.split("=")
93
- if isNestedKey? key
94
- putNestedKey key, value
95
- else
96
- set_config(key => Utils.smart_unescape(value))
97
- end
98
- end
99
- end
81
+ # Cloudinary account config
82
+ #
83
+ # @param [Hash] new_config If +new_config+ is passed, Account Config will be updated with it
84
+ # @yieldparam [OpenStruct] Account config can be updated in the block
85
+ #
86
+ # @return [OpenStruct]
87
+ def self.account_config(new_config=nil)
88
+ @@account_config ||= make_new_config(AccountConfig)
100
89
 
101
- def self.putNestedKey(key, value)
102
- chain = key.split(/[\[\]]+/).reject { |i| i.empty? }
103
- outer = @@config
104
- lastKey = chain.pop()
105
- chain.each do |innerKey|
106
- inner = outer[innerKey]
107
- if inner.nil?
108
- inner = OpenStruct.new
109
- outer[innerKey] = inner
110
- end
111
- outer = inner
112
- end
113
- outer[lastKey] = value
90
+ @@account_config.update(new_config) if new_config
91
+ yield @@account_config if block_given?
92
+
93
+ @@account_config
114
94
  end
115
95
 
96
+ def self.config_from_url(url)
97
+ config.load_from_url(url)
98
+ end
116
99
 
117
- def self.isNestedKey?(key)
118
- /\w+\[\w+\]/ =~ key
100
+ def self.config_from_account_url(url)
101
+ account_config.load_from_url(url)
119
102
  end
120
103
 
121
104
  def self.app_root
@@ -129,22 +112,6 @@ module Cloudinary
129
112
 
130
113
  private
131
114
 
132
- def self.config_from_env
133
- # Heroku support
134
- if ENV["CLOUDINARY_CLOUD_NAME"]
135
- config_keys = ENV.keys.select! { |key| key.start_with? "CLOUDINARY_" }
136
- config_keys -= ["CLOUDINARY_URL"] # ignore it when explicit options are passed
137
- config_keys.each do |full_key|
138
- conf_key = full_key["CLOUDINARY_".length..-1].downcase # convert "CLOUDINARY_CONFIG_NAME" to "config_name"
139
- conf_val = ENV[full_key]
140
- conf_val = conf_val == 'true' if %w[true false].include?(conf_val) # cast relevant boolean values
141
- set_config(conf_key => conf_val)
142
- end
143
- elsif ENV["CLOUDINARY_URL"]
144
- config_from_url(ENV["CLOUDINARY_URL"])
145
- end
146
- end
147
-
148
115
  def self.config_env
149
116
  return ENV["CLOUDINARY_ENV"] if ENV["CLOUDINARY_ENV"]
150
117
  return Rails.env if defined? Rails::env
@@ -159,6 +126,20 @@ module Cloudinary
159
126
  def self.set_config(new_config)
160
127
  new_config.each{|k,v| @@config.send(:"#{k}=", v) if !v.nil?}
161
128
  end
129
+
130
+ # Builds config from yaml file, extends it with specific module and loads configuration from environment variable
131
+ #
132
+ # @param [Module] config_module Config is extended with this module after being built
133
+ #
134
+ # @return [OpenStruct]
135
+ def self.make_new_config(config_module)
136
+ OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {})).tap do |config|
137
+ config.extend(config_module)
138
+ config.load_config_from_env
139
+ end
140
+ end
141
+
142
+ private_class_method :make_new_config
162
143
  end
163
144
  # Prevent require loop if included after Rails is already initialized.
164
145
  require "cloudinary/helper" if defined?(::ActionView::Base)
@@ -0,0 +1,231 @@
1
+ class Cloudinary::AccountApi
2
+ extend Cloudinary::BaseApi
3
+
4
+ # Creates a new sub-account. Any users that have access to all sub-accounts will also automatically have access to the
5
+ # new sub-account.
6
+ # @param [String] name The display name as shown in the management console
7
+ # @param [String] cloud_name A case-insensitive cloud name comprised of alphanumeric and underscore characters.
8
+ # Generates an error if the specified cloud name is not unique across all Cloudinary accounts.
9
+ # Note: Once created, the name can only be changed for accounts with fewer than 1000 assets.
10
+ # @param [Object] custom_attributes Any custom attributes you want to associate with the sub-account
11
+ # @param [Boolean] enabled Whether to create the account as enabled (default is enabled)
12
+ # @param [String] base_account ID of sub-account from which to copy settings
13
+ # @param [Object] options additional options
14
+ def self.create_sub_account(name, cloud_name = nil, custom_attributes = {}, enabled = nil, base_account = nil, options = {})
15
+ params = {
16
+ name: name,
17
+ cloud_name: cloud_name,
18
+ custom_attributes: custom_attributes,
19
+ enabled: enabled,
20
+ base_sub_account_id: base_account
21
+ }
22
+
23
+ call_account_api(:post, 'sub_accounts', params, options.merge(content_type: :json))
24
+ end
25
+
26
+ # Updates the specified details of the sub-account.
27
+ # @param [String] sub_account_id The ID of the sub-account.
28
+ # @param [String] name The display name as shown in the management console
29
+ # @param [String] cloud_name A case-insensitive cloud name comprised of alphanumeric and underscore characters.
30
+ # Generates an error if the specified cloud name is not unique across all Cloudinary accounts.
31
+ # Note: Once created, the name can only be changed for accounts with fewer than 1000 assets.
32
+ # @param [Object] custom_attributes Any custom attributes you want to associate with the sub-account, as a map/hash
33
+ # of key/value pairs.
34
+ # @param [Boolean] enabled Whether the sub-account is enabled.
35
+ # @param [Object] options additional options
36
+ def self.update_sub_account(sub_account_id, name = nil, cloud_name = nil, custom_attributes = nil, enabled = nil, options = {})
37
+ params = {
38
+ name: name,
39
+ cloud_name: cloud_name,
40
+ custom_attributes: custom_attributes,
41
+ enabled: enabled
42
+ }
43
+
44
+ call_account_api(:put, ['sub_accounts', sub_account_id], params, options.merge(content_type: :json))
45
+ end
46
+
47
+ # Lists sub-accounts.
48
+ # @param [Boolean] enabled Whether to only return enabled sub-accounts (true) or disabled accounts (false).
49
+ # Default: all accounts are returned (both enabled and disabled).
50
+ # @param [Array<String>] ids A list of up to 100 sub-account IDs. When provided, other parameters are ignored.
51
+ # @param [String] prefix Returns accounts where the name begins with the specified case-insensitive string.
52
+ # @param [Object] options additional options
53
+ def self.sub_accounts(enabled = nil, ids = [], prefix = nil, options = {})
54
+ params = {
55
+ enabled: enabled,
56
+ ids: ids,
57
+ prefix: prefix
58
+ }
59
+
60
+ call_account_api(:get, 'sub_accounts', params, options.merge(content_type: :json))
61
+ end
62
+
63
+ # Retrieves the details of the specified sub-account.
64
+ # @param [String] sub_account_id The ID of the sub-account.
65
+ # @param [Object] options additional options
66
+ def self.sub_account(sub_account_id, options = {})
67
+ call_account_api(:get, ['sub_accounts', sub_account_id], {}, options.merge(content_type: :json))
68
+ end
69
+
70
+ # Deletes the specified sub-account. Supported only for accounts with fewer than 1000 assets.
71
+ # @param [String] sub_account_id The ID of the sub-account.
72
+ # @param [Object] options additional options
73
+ def self.delete_sub_account(sub_account_id, options = {})
74
+ call_account_api(:delete, ['sub_accounts', sub_account_id], {}, options)
75
+ end
76
+
77
+ # Creates a new user in the account.
78
+ # @param [String] name The name of the user.
79
+ # @param [String] email A unique email address, which serves as the login name and notification address.
80
+ # @param [String] role The role to assign. Possible values: master_admin, admin, billing, technical_admin, reports,
81
+ # media_library_admin, media_library_user
82
+ # @param [Array<String>] sub_account_ids The list of sub-account IDs that this user can access.
83
+ # Note: This parameter is ignored if the role is specified as master_admin.
84
+ # @param [Object] options additional options
85
+ def self.create_user(name, email, role, sub_account_ids = [], options = {})
86
+ params = {
87
+ name: name,
88
+ email: email,
89
+ role: role,
90
+ sub_account_ids: sub_account_ids
91
+ }
92
+
93
+ call_account_api(:post, 'users', params, options.merge(content_type: :json))
94
+ end
95
+
96
+ # Deletes an existing user.
97
+ # @param [String] user_id The ID of the user to delete.
98
+ # @param [Object] options additional options
99
+ def self.delete_user(user_id, options = {})
100
+ call_account_api(:delete, ['users', user_id], {}, options)
101
+ end
102
+
103
+ # Updates the details of the specified user.
104
+ # @param [String] user_id The ID of the user to update.
105
+ # @param [String] name The name of the user.
106
+ # @param [String] email A unique email address, which serves as the login name and notification address.
107
+ # @param [String] role The role to assign. Possible values: master_admin, admin, billing, technical_admin, reports,
108
+ # media_library_admin, media_library_user
109
+ # @param [Array<String>] sub_account_ids The list of sub-account IDs that this user can access.
110
+ # Note: This parameter is ignored if the role is specified as master_admin.
111
+ # @param [Object] options additional options
112
+ def self.update_user(user_id, name = nil, email = nil, role = nil, sub_account_ids = nil, options = {})
113
+ params = {
114
+ name: name,
115
+ email: email,
116
+ role: role,
117
+ sub_account_ids: sub_account_ids
118
+ }
119
+
120
+ call_account_api(:put, ['users', user_id], params, options.merge(content_type: :json))
121
+ end
122
+
123
+ # Returns the user with the specified ID.
124
+ # @param [String] user_id The ID of the user.
125
+ # @param [Object] options additional options
126
+ def self.user(user_id, options = {})
127
+ call_account_api(:get, ['users', user_id], {}, options.merge(content_type: :json))
128
+ end
129
+
130
+ # Lists users in the account.
131
+ # @param [Boolean] pending Whether to only return pending users. Default: all users
132
+ # @param [Array<String>] user_ids A list of up to 100 user IDs. When provided, other parameters are ignored.
133
+ # @param [String] prefix Returns users where the name or email address begins with the specified case-insensitive string.
134
+ # @param [String] sub_account_id Only returns users who have access to the specified account.
135
+ # @param [Object] options additional options
136
+ def self.users(pending = nil, user_ids = [], prefix = nil, sub_account_id = nil, options = {})
137
+ params = if user_ids && user_ids.count > 0
138
+ {
139
+ ids: user_ids
140
+ }
141
+ else
142
+ {
143
+ prefix: prefix,
144
+ sub_account_id: sub_account_id,
145
+ pending: pending
146
+ }
147
+ end
148
+
149
+ call_account_api(:get, 'users', params, options.merge(content_type: :json))
150
+ end
151
+
152
+ # Creates a new user group.
153
+ # @param [String] name The name for the user group.
154
+ # @param [Object] options additional options
155
+ def self.create_user_group(name, options = {})
156
+ params = {
157
+ name: name
158
+ }
159
+
160
+ call_account_api(:post, 'user_groups', params, options.merge(content_type: :json))
161
+ end
162
+
163
+ # Updates the specified user group.
164
+ # @param [String] group_id The ID of the user group to update.
165
+ # @param [String] name The name for the user group.
166
+ # @param [Object] options additional options
167
+ def self.update_user_group(group_id, name, options = {})
168
+ params = {
169
+ name: name
170
+ }
171
+
172
+ call_account_api(:put, ['user_groups', group_id], params, options.merge(content_type: :json))
173
+ end
174
+
175
+ # Adds a user to a group with the specified ID.
176
+ # @param [String] group_id The ID of the user group.
177
+ # @param [String] user_id The ID of the user.
178
+ # @param [Object] options additional options
179
+ def self.add_user_to_group(group_id, user_id, options = {})
180
+ call_account_api(:post, ['user_groups', group_id, 'users', user_id], {}, options.merge(content_type: :json))
181
+ end
182
+
183
+ # Removes a user from a group with the specified ID.
184
+ # @param [String] group_id The ID of the user group.
185
+ # @param [String] user_id The ID of the user.
186
+ # @param [Object] options additional options
187
+ def self.remove_user_from_group(group_id, user_id, options = {})
188
+ call_account_api(:delete, ['user_groups', group_id, 'users', user_id], {}, options.merge(content_type: :json))
189
+ end
190
+
191
+ # Deletes the user group with the specified ID.
192
+ # @param [String] group_id The ID of the user group to delete.
193
+ # @param [Object] options additional options
194
+ def self.delete_user_group(group_id, options = {})
195
+ call_account_api(:delete, ['user_groups', group_id], {}, options)
196
+ end
197
+
198
+ # Lists user groups in the account.
199
+ # @param [Object] options additional options
200
+ def self.user_groups(options = {})
201
+ call_account_api(:get, 'user_groups', {}, options.merge(content_type: :json))
202
+ end
203
+
204
+ # Retrieves the details of the specified user group.
205
+ # @param [String] group_id The ID of the user group to retrieve.
206
+ # @param [Object] options additional options
207
+ def self.user_group(group_id, options = {})
208
+ call_account_api(:get, ['user_groups', group_id], {}, options.merge(content_type: :json))
209
+ end
210
+
211
+ # Lists users in the specified user group.
212
+ # @param [String] group_id The ID of the user group.
213
+ # @param [Object] options additional options
214
+ def self.user_group_users(group_id, options = {})
215
+ call_account_api(:get, ['user_groups', group_id, 'users'], {}, options.merge(content_type: :json))
216
+ end
217
+
218
+ def self.call_account_api(method, uri, params, options)
219
+ account_id = options[:account_id] || Cloudinary.account_config.account_id || raise('Must supply account_id')
220
+ api_key = options[:provisioning_api_key] || Cloudinary.account_config.provisioning_api_key || raise('Must supply provisioning api_key')
221
+ api_secret = options[:provisioning_api_secret] || Cloudinary.account_config.provisioning_api_secret || raise('Must supply provisioning api_secret')
222
+
223
+ params.reject! { |_, v| v.nil? }
224
+
225
+ call_cloudinary_api(method, uri, api_key, api_secret, params, options) do |cloudinary, inner_uri|
226
+ [cloudinary, 'v1_1', 'provisioning', 'accounts', account_id, inner_uri]
227
+ end
228
+ end
229
+
230
+ private_class_method :call_account_api
231
+ end
@@ -0,0 +1,30 @@
1
+ module Cloudinary
2
+ module AccountConfig
3
+ include BaseConfig
4
+
5
+ ENV_URL = "CLOUDINARY_ACCOUNT_URL"
6
+ SCHEME = "account"
7
+
8
+ def load_config_from_env
9
+ load_from_url(ENV[ENV_URL]) if ENV[ENV_URL]
10
+ end
11
+
12
+ private
13
+
14
+ def env_url
15
+ ENV_URL
16
+ end
17
+
18
+ def expected_scheme
19
+ SCHEME
20
+ end
21
+
22
+ def config_from_parsed_url(parsed_url)
23
+ {
24
+ "account_id" => parsed_url.host,
25
+ "provisioning_api_key" => parsed_url.user,
26
+ "provisioning_api_secret" => parsed_url.password
27
+ }
28
+ end
29
+ end
30
+ end
@@ -1,37 +1,28 @@
1
- require 'rest_client'
2
- require 'json'
3
-
4
1
  class Cloudinary::Api
5
- class Error < CloudinaryException; end
6
- class NotFound < Error; end
7
- class NotAllowed < Error; end
8
- class AlreadyExists < Error; end
9
- class RateLimited < Error; end
10
- class BadRequest < Error; end
11
- class GeneralError < Error; end
12
- class AuthorizationRequired < Error; end
13
-
14
- class Response < Hash
15
- attr_reader :rate_limit_reset_at, :rate_limit_remaining, :rate_limit_allowed
16
-
17
- def initialize(response=nil)
18
- if response
19
- # This sets the instantiated self as the response Hash
20
- update Cloudinary::Api.parse_json_response response
21
-
22
- @rate_limit_allowed = response.headers[:x_featureratelimit_limit].to_i if response.headers[:x_featureratelimit_limit]
23
- @rate_limit_reset_at = Time.parse(response.headers[:x_featureratelimit_reset]) if response.headers[:x_featureratelimit_reset]
24
- @rate_limit_remaining = response.headers[:x_featureratelimit_remaining].to_i if response.headers[:x_featureratelimit_remaining]
25
- end
26
- end
27
- end
2
+ extend Cloudinary::BaseApi
28
3
 
29
4
  def self.ping(options={})
30
5
  call_api(:get, "ping", {}, options)
31
6
  end
32
7
 
8
+ # Gets account usage details
9
+ #
10
+ # Get a report on the status of your Cloudinary account usage details, including
11
+ # storage, bandwidth, requests, number of resources, and add-on usage.
12
+ # Note that numbers are updated periodically.
13
+ #
14
+ # @see https://cloudinary.com/documentation/admin_api#get_account_usage_details Get account usage details
15
+ #
16
+ # @param [Hash] options Additional options
17
+ # @return [Cloudinary::Api::Response]
18
+ # @raise [Cloudinary::Api:Error]
33
19
  def self.usage(options={})
34
- call_api(:get, "usage", {}, options)
20
+ uri = 'usage'
21
+ date = options[:date]
22
+
23
+ uri += "/#{Cloudinary::Utils.to_usage_api_date_format(date)}" unless date.nil?
24
+
25
+ call_api(:get, uri, {}, options)
35
26
  end
36
27
 
37
28
  def self.resource_types(options={})
@@ -489,44 +480,15 @@ class Cloudinary::Api
489
480
  protected
490
481
 
491
482
  def self.call_api(method, uri, params, options)
492
- cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || "https://api.cloudinary.com"
493
- cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise("Must supply cloud_name")
494
- api_key = options[:api_key] || Cloudinary.config.api_key || raise("Must supply api_key")
495
- api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
496
- timeout = options[:timeout] || Cloudinary.config.timeout || 60
497
- uri = Cloudinary::Utils.smart_escape(uri)
498
- api_url = [cloudinary, "v1_1", cloud_name, uri].join("/")
499
- # Add authentication
500
- api_url.sub!(%r(^(https?://)), "\\1#{api_key}:#{api_secret}@")
501
-
502
- headers = { "User-Agent" => Cloudinary::USER_AGENT }
503
- if options[:content_type]== :json
504
- payload = params.to_json
505
- headers.merge!("Content-Type"=> 'application/json', "Accept"=> 'application/json')
506
- else
507
- payload = params.reject { |k, v| v.nil? || v=="" }
508
- end
509
- call_json_api(method, api_url, payload, timeout, headers)
510
- end
511
-
512
- def self.call_json_api(method, api_url, payload, timeout, headers)
513
- RestClient::Request.execute(:method => method, :url => api_url, :payload => payload, :timeout => timeout, :headers => headers) do
514
- |response, request, tmpresult|
515
- return Response.new(response) if response.code == 200
516
- exception_class = case response.code
517
- when 400 then BadRequest
518
- when 401 then AuthorizationRequired
519
- when 403 then NotAllowed
520
- when 404 then NotFound
521
- when 409 then AlreadyExists
522
- when 420 then RateLimited
523
- when 500 then GeneralError
524
- else raise GeneralError.new("Server returned unexpected status code - #{response.code} - #{response.body}")
525
- end
526
- json = parse_json_response(response)
527
- raise exception_class.new(json["error"]["message"])
483
+ cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise('Must supply cloud_name')
484
+ api_key = options[:api_key] || Cloudinary.config.api_key || raise('Must supply api_key')
485
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise('Must supply api_secret')
486
+
487
+ call_cloudinary_api(method, uri, api_key, api_secret, params, options) do |cloudinary, inner_uri|
488
+ [cloudinary, 'v1_1', cloud_name, inner_uri]
528
489
  end
529
490
  end
491
+
530
492
  def self.parse_json_response(response)
531
493
  return Cloudinary::Utils.json_decode(response.body)
532
494
  rescue => e
@@ -593,5 +555,4 @@ class Cloudinary::Api
593
555
  params[by_key] = value
594
556
  call_api("post", "resources/#{resource_type}/#{type}/update_access_mode", params, options)
595
557
  end
596
-
597
558
  end
@@ -0,0 +1,79 @@
1
+ require "rest_client"
2
+ require "json"
3
+
4
+ module Cloudinary::BaseApi
5
+ class Error < CloudinaryException; end
6
+ class NotFound < Error; end
7
+ class NotAllowed < Error; end
8
+ class AlreadyExists < Error; end
9
+ class RateLimited < Error; end
10
+ class BadRequest < Error; end
11
+ class GeneralError < Error; end
12
+ class AuthorizationRequired < Error; end
13
+
14
+ class Response < Hash
15
+ attr_reader :rate_limit_reset_at, :rate_limit_remaining, :rate_limit_allowed
16
+
17
+ def initialize(response=nil)
18
+ if response
19
+ # This sets the instantiated self as the response Hash
20
+ update Cloudinary::Api.parse_json_response response
21
+
22
+ @rate_limit_allowed = response.headers[:x_featureratelimit_limit].to_i if response.headers[:x_featureratelimit_limit]
23
+ @rate_limit_reset_at = Time.parse(response.headers[:x_featureratelimit_reset]) if response.headers[:x_featureratelimit_reset]
24
+ @rate_limit_remaining = response.headers[:x_featureratelimit_remaining].to_i if response.headers[:x_featureratelimit_remaining]
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.extended(base)
30
+ [Error, NotFound, NotAllowed, AlreadyExists, RateLimited, BadRequest, GeneralError, AuthorizationRequired, Response].each do |constant|
31
+ base.const_set(constant.name.split("::").last, constant)
32
+ end
33
+ end
34
+
35
+ def call_json_api(method, api_url, payload, timeout, headers, proxy = nil, user = nil, password = nil)
36
+ RestClient::Request.execute(method: method,
37
+ url: api_url,
38
+ payload: payload,
39
+ timeout: timeout,
40
+ headers: headers,
41
+ proxy: proxy,
42
+ user: user,
43
+ password: password) do |response|
44
+ return Response.new(response) if response.code == 200
45
+ exception_class = case response.code
46
+ when 400 then BadRequest
47
+ when 401 then AuthorizationRequired
48
+ when 403 then NotAllowed
49
+ when 404 then NotFound
50
+ when 409 then AlreadyExists
51
+ when 420 then RateLimited
52
+ when 500 then GeneralError
53
+ else raise GeneralError.new("Server returned unexpected status code - #{response.code} - #{response.body}")
54
+ end
55
+ json = Cloudinary::Api.parse_json_response(response)
56
+ raise exception_class.new(json["error"]["message"])
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def call_cloudinary_api(method, uri, user, password, params, options, &api_url_builder)
63
+ cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || 'https://api.cloudinary.com'
64
+ api_url = Cloudinary::Utils.smart_escape(api_url_builder.call(cloudinary, uri).flatten.join('/'))
65
+ timeout = options[:timeout] || Cloudinary.config.timeout || 60
66
+ proxy = options[:api_proxy] || Cloudinary.config.api_proxy
67
+
68
+ headers = { "User-Agent" => Cloudinary::USER_AGENT }
69
+
70
+ if options[:content_type] == :json
71
+ payload = params.to_json
72
+ headers.merge!("Content-Type" => "application/json", "Accept" => "application/json")
73
+ else
74
+ payload = params.reject { |_, v| v.nil? || v == "" }
75
+ end
76
+
77
+ call_json_api(method, api_url, payload, timeout, headers, proxy, user, password)
78
+ end
79
+ end
@@ -0,0 +1,70 @@
1
+ module Cloudinary
2
+ module BaseConfig
3
+ def load_from_url(url)
4
+ return unless url && !url.empty?
5
+
6
+ parsed_url = URI.parse(url)
7
+ scheme = parsed_url.scheme.to_s.downcase
8
+
9
+ if expected_scheme != scheme
10
+ raise(CloudinaryException,
11
+ "Invalid #{env_url} scheme. Expecting to start with '#{expected_scheme}://'")
12
+ end
13
+
14
+ update(config_from_parsed_url(parsed_url))
15
+ setup_from_parsed_url(parsed_url)
16
+ end
17
+
18
+ def update(new_config = {})
19
+ new_config.each{ |k,v| public_send(:"#{k}=", v) unless v.nil?}
20
+ end
21
+
22
+ def load_config_from_env
23
+ raise NotImplementedError
24
+ end
25
+
26
+ private
27
+
28
+ def config_from_parsed_url(parsed_url)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def env_url
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def expected_scheme
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def put_nested_key(key, value)
41
+ chain = key.split(/[\[\]]+/).reject(&:empty?)
42
+ outer = self
43
+ lastKey = chain.pop
44
+ chain.each do |innerKey|
45
+ inner = outer[innerKey]
46
+ if inner.nil?
47
+ inner = OpenStruct.new
48
+ outer[innerKey] = inner
49
+ end
50
+ outer = inner
51
+ end
52
+ outer[lastKey] = value
53
+ end
54
+
55
+ def is_nested_key?(key)
56
+ /\w+\[\w+\]/ =~ key
57
+ end
58
+
59
+ def setup_from_parsed_url(parsed_url)
60
+ parsed_url.query.to_s.split("&").each do |param|
61
+ key, value = param.split("=")
62
+ if is_nested_key? key
63
+ put_nested_key key, value
64
+ else
65
+ update(key => Utils.smart_unescape(value))
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,43 @@
1
+ module Cloudinary
2
+ module Config
3
+ include BaseConfig
4
+
5
+ ENV_URL = "CLOUDINARY_URL"
6
+ SCHEME = "cloudinary"
7
+
8
+ def load_config_from_env
9
+ if ENV["CLOUDINARY_CLOUD_NAME"]
10
+ config_keys = ENV.keys.select! { |key| key.start_with? "CLOUDINARY_" }
11
+ config_keys -= ["CLOUDINARY_URL"] # ignore it when explicit options are passed
12
+ config_keys.each do |full_key|
13
+ conf_key = full_key["CLOUDINARY_".length..-1].downcase # convert "CLOUDINARY_CONFIG_NAME" to "config_name"
14
+ conf_val = ENV[full_key]
15
+ conf_val = conf_val == 'true' if %w[true false].include?(conf_val) # cast relevant boolean values
16
+ update(conf_key => conf_val)
17
+ end
18
+ elsif ENV[ENV_URL]
19
+ load_from_url(ENV[ENV_URL])
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def env_url
26
+ ENV_URL
27
+ end
28
+
29
+ def expected_scheme
30
+ SCHEME
31
+ end
32
+
33
+ def config_from_parsed_url(parsed_url)
34
+ {
35
+ "cloud_name" => parsed_url.host,
36
+ "api_key" => parsed_url.user,
37
+ "api_secret" => parsed_url.password,
38
+ "private_cdn" => !parsed_url.path.blank?,
39
+ "secure_distribution" => parsed_url.path[1..-1]
40
+ }
41
+ end
42
+ end
43
+ end
@@ -346,6 +346,7 @@ class Cloudinary::Uploader
346
346
  params[:signature] = Cloudinary::Utils.api_sign_request(params.reject { |k, v| non_signable.include?(k) }, api_secret)
347
347
  params[:api_key] = api_key
348
348
  end
349
+ proxy = options[:api_proxy] || Cloudinary.config.api_proxy
349
350
  timeout = options.fetch(:timeout) { Cloudinary.config.to_h.fetch(:timeout, 60) }
350
351
 
351
352
  result = nil
@@ -355,7 +356,7 @@ class Cloudinary::Uploader
355
356
  headers['Content-Range'] = options[:content_range] if options[:content_range]
356
357
  headers['X-Unique-Upload-Id'] = options[:unique_upload_id] if options[:unique_upload_id]
357
358
  headers.merge!(options[:extra_headers]) if options[:extra_headers]
358
- RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers) do
359
+ RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers, :proxy => proxy) do
359
360
  |response, request, tmpresult|
360
361
  raise CloudinaryException, "Server returned unexpected status code - #{response.code} - #{response.body}" unless [200, 400, 401, 403, 404, 500].include?(response.code)
361
362
  begin
@@ -1149,6 +1149,20 @@ class Cloudinary::Utils
1149
1149
  REMOTE_URL_REGEX === url
1150
1150
  end
1151
1151
 
1152
+ # Format date in a format accepted by the usage API (e.g., 31-12-2020) if
1153
+ # passed value is of type Date, otherwise return the string representation of
1154
+ # the input.
1155
+ #
1156
+ # @param [Date|Object] date
1157
+ # @return [String]
1158
+ def self.to_usage_api_date_format(date)
1159
+ if date.is_a?(Date)
1160
+ date.strftime('%d-%m-%Y')
1161
+ else
1162
+ date.to_s
1163
+ end
1164
+ end
1165
+
1152
1166
  # Computes a short or long signature based on a message and secret
1153
1167
  # @param [String] message The string to sign
1154
1168
  # @param [String] secret A secret that will be added to the message when signing
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.18.1"
3
+ VERSION = "1.19.0"
4
4
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudinary
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.18.1
4
+ version: 1.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nadav Soferman
8
8
  - Itai Lahan
9
9
  - Tal Lev-Ami
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-09-30 00:00:00.000000000 Z
13
+ date: 2021-03-05 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws_cf_signer
@@ -32,14 +32,14 @@ dependencies:
32
32
  requirements:
33
33
  - - ">="
34
34
  - !ruby/object:Gem::Version
35
- version: '0'
35
+ version: 2.0.0
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - ">="
41
41
  - !ruby/object:Gem::Version
42
- version: '0'
42
+ version: 2.0.0
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: actionpack
45
45
  requirement: !ruby/object:Gem::Requirement
@@ -156,16 +156,16 @@ dependencies:
156
156
  name: rubyzip
157
157
  requirement: !ruby/object:Gem::Requirement
158
158
  requirements:
159
- - - "<="
159
+ - - ">="
160
160
  - !ruby/object:Gem::Version
161
- version: 1.2.0
161
+ version: '0'
162
162
  type: :development
163
163
  prerelease: false
164
164
  version_requirements: !ruby/object:Gem::Requirement
165
165
  requirements:
166
- - - "<="
166
+ - - ">="
167
167
  - !ruby/object:Gem::Version
168
- version: 1.2.0
168
+ version: '0'
169
169
  - !ruby/object:Gem::Dependency
170
170
  name: simplecov
171
171
  requirement: !ruby/object:Gem::Requirement
@@ -204,8 +204,12 @@ files:
204
204
  - lib/active_storage/blob_key.rb
205
205
  - lib/active_storage/service/cloudinary_service.rb
206
206
  - lib/cloudinary.rb
207
+ - lib/cloudinary/account_api.rb
208
+ - lib/cloudinary/account_config.rb
207
209
  - lib/cloudinary/api.rb
208
210
  - lib/cloudinary/auth_token.rb
211
+ - lib/cloudinary/base_api.rb
212
+ - lib/cloudinary/base_config.rb
209
213
  - lib/cloudinary/blob.rb
210
214
  - lib/cloudinary/cache.rb
211
215
  - lib/cloudinary/cache/breakpoints_cache.rb
@@ -219,6 +223,7 @@ files:
219
223
  - lib/cloudinary/carrier_wave/remote.rb
220
224
  - lib/cloudinary/carrier_wave/storage.rb
221
225
  - lib/cloudinary/cloudinary_controller.rb
226
+ - lib/cloudinary/config.rb
222
227
  - lib/cloudinary/downloader.rb
223
228
  - lib/cloudinary/engine.rb
224
229
  - lib/cloudinary/exceptions.rb
@@ -256,7 +261,7 @@ homepage: http://cloudinary.com
256
261
  licenses:
257
262
  - MIT
258
263
  metadata: {}
259
- post_install_message:
264
+ post_install_message:
260
265
  rdoc_options: []
261
266
  require_paths:
262
267
  - lib
@@ -271,8 +276,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
271
276
  - !ruby/object:Gem::Version
272
277
  version: '0'
273
278
  requirements: []
274
- rubygems_version: 3.1.4
275
- signing_key:
279
+ rubygems_version: 3.1.2
280
+ signing_key:
276
281
  specification_version: 4
277
282
  summary: Client library for easily using the Cloudinary service
278
283
  test_files: []