cloudinary 1.18.1 → 1.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/cloudinary.gemspec +8 -3
- data/lib/active_storage/service/cloudinary_service.rb +11 -1
- data/lib/cloudinary.rb +45 -64
- data/lib/cloudinary/account_api.rb +231 -0
- data/lib/cloudinary/account_config.rb +30 -0
- data/lib/cloudinary/api.rb +25 -64
- data/lib/cloudinary/base_api.rb +79 -0
- data/lib/cloudinary/base_config.rb +70 -0
- data/lib/cloudinary/config.rb +43 -0
- data/lib/cloudinary/uploader.rb +2 -1
- data/lib/cloudinary/utils.rb +14 -0
- data/lib/cloudinary/version.rb +1 -1
- metadata +17 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 635b7d9336fcde0655fd8b348bda1cdf443a60e123991a1d6197a5a55e9d159b
|
4
|
+
data.tar.gz: 8cef3ea547da816bf1125243e04ed72834414f8529a27579e2ba3916571d79bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 =
|
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
|
-
|
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"
|
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
|
-
|
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
|
-
|
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
|
-
|
70
|
-
yield
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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.
|
118
|
-
|
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
|
data/lib/cloudinary/api.rb
CHANGED
@@ -1,37 +1,28 @@
|
|
1
|
-
require 'rest_client'
|
2
|
-
require 'json'
|
3
|
-
|
4
1
|
class Cloudinary::Api
|
5
|
-
|
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
|
-
|
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
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
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
|
data/lib/cloudinary/uploader.rb
CHANGED
@@ -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
|
data/lib/cloudinary/utils.rb
CHANGED
@@ -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
|
data/lib/cloudinary/version.rb
CHANGED
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.
|
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:
|
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:
|
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:
|
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:
|
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:
|
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.
|
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: []
|