cloudinary 1.18.0 → 1.21.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.
@@ -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={})
@@ -43,25 +34,25 @@ class Cloudinary::Api
43
34
  type = options[:type]
44
35
  uri = "resources/#{resource_type}"
45
36
  uri += "/#{type}" unless type.blank?
46
- call_api(:get, uri, only(options, :next_cursor, :max_results, :prefix, :tags, :context, :moderations, :direction, :start_at), options)
37
+ call_api(:get, uri, only(options, :next_cursor, :max_results, :prefix, :tags, :context, :moderations, :direction, :start_at, :metadata), options)
47
38
  end
48
39
 
49
40
  def self.resources_by_tag(tag, options={})
50
41
  resource_type = options[:resource_type] || "image"
51
42
  uri = "resources/#{resource_type}/tags/#{tag}"
52
- call_api(:get, uri, only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction), options)
43
+ call_api(:get, uri, only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction, :metadata), options)
53
44
  end
54
45
 
55
46
  def self.resources_by_moderation(kind, status, options={})
56
47
  resource_type = options[:resource_type] || "image"
57
48
  uri = "resources/#{resource_type}/moderations/#{kind}/#{status}"
58
- call_api(:get, uri, only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction), options)
49
+ call_api(:get, uri, only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction, :metadata), options)
59
50
  end
60
51
 
61
52
  def self.resources_by_context(key, value=nil, options={})
62
53
  resource_type = options[:resource_type] || "image"
63
54
  uri = "resources/#{resource_type}/context"
64
- params = only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction,:key,:value)
55
+ params = only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction, :key, :value, :metadata)
65
56
  params[:key] = key
66
57
  params[:value] = value
67
58
  call_api(:get, uri, params, options)
@@ -91,7 +82,8 @@ class Cloudinary::Api
91
82
  :phash,
92
83
  :quality_analysis,
93
84
  :derived_next_cursor,
94
- :accessibility_analysis
85
+ :accessibility_analysis,
86
+ :versions
95
87
  ), options)
96
88
  end
97
89
 
@@ -99,7 +91,7 @@ class Cloudinary::Api
99
91
  resource_type = options[:resource_type] || "image"
100
92
  type = options[:type] || "upload"
101
93
  uri = "resources/#{resource_type}/#{type}/restore"
102
- call_api(:post, uri, { :public_ids => public_ids }, options)
94
+ call_api(:post, uri, { :public_ids => public_ids, :versions => options[:versions] }, options)
103
95
  end
104
96
 
105
97
  def self.update(public_id, options={})
@@ -185,24 +177,32 @@ class Cloudinary::Api
185
177
  end
186
178
 
187
179
  def self.transformation(transformation, options={})
188
- call_api(:get, "transformations/#{transformation_string(transformation)}", only(options, :next_cursor, :max_results), options)
180
+ params = only(options, :next_cursor, :max_results)
181
+ params[:transformation] = Cloudinary::Utils.build_eager(transformation)
182
+ call_api(:get, "transformations", params, options)
189
183
  end
190
184
 
191
185
  def self.delete_transformation(transformation, options={})
192
- call_api(:delete, "transformations/#{transformation_string(transformation)}", {}, options)
186
+ call_api(:delete, "transformations", {:transformation => Cloudinary::Utils.build_eager(transformation)}, options)
193
187
  end
194
188
 
195
189
  # updates - supports:
196
190
  # "allowed_for_strict" boolean
197
191
  # "unsafe_update" transformation params - updates a named transformation parameters without regenerating existing images
198
192
  def self.update_transformation(transformation, updates, options={})
199
- params = only(updates, :allowed_for_strict)
200
- params[:unsafe_update] = transformation_string(updates[:unsafe_update]) if updates[:unsafe_update]
201
- call_api(:put, "transformations/#{transformation_string(transformation)}", params, options)
193
+ params = only(updates, :allowed_for_strict)
194
+ params[:unsafe_update] = Cloudinary::Utils.build_eager(updates[:unsafe_update]) if updates[:unsafe_update]
195
+ params[:transformation] = Cloudinary::Utils.build_eager(transformation)
196
+ call_api(:put, "transformations", params, options)
202
197
  end
203
198
 
204
199
  def self.create_transformation(name, definition, options={})
205
- call_api(:post, "transformations/#{name}", { :transformation => transformation_string(definition) }, options)
200
+ params = {
201
+ :name => name,
202
+ :transformation => Cloudinary::Utils.build_eager(definition)
203
+ }
204
+
205
+ call_api(:post, "transformations", params, options)
206
206
  end
207
207
 
208
208
  # upload presets
@@ -220,12 +220,12 @@ class Cloudinary::Api
220
220
 
221
221
  def self.update_upload_preset(name, options={})
222
222
  params = Cloudinary::Uploader.build_upload_params(options)
223
- call_api(:put, "upload_presets/#{name}", params.merge(only(options, :unsigned, :disallow_public_id)), options)
223
+ call_api(:put, "upload_presets/#{name}", params.merge(only(options, :unsigned, :disallow_public_id, :live)), options)
224
224
  end
225
225
 
226
226
  def self.create_upload_preset(options={})
227
227
  params = Cloudinary::Uploader.build_upload_params(options)
228
- call_api(:post, "upload_presets", params.merge(only(options, :name, :unsigned, :disallow_public_id)), options)
228
+ call_api(:post, "upload_presets", params.merge(only(options, :name, :unsigned, :disallow_public_id, :live)), options)
229
229
  end
230
230
 
231
231
  def self.root_folders(options={})
@@ -486,47 +486,35 @@ class Cloudinary::Api
486
486
  call_metadata_api(:post, uri, params, options)
487
487
  end
488
488
 
489
+ # Reorders metadata field datasource. Currently supports only value.
490
+ #
491
+ # @param [String] field_external_id The ID of the metadata field
492
+ # @param [String] order_by Criteria for the order. Currently supports only value
493
+ # @param [String] direction Optional (gets either asc or desc)
494
+ # @param [Hash] options Configuration options
495
+ #
496
+ # @return [Cloudinary::Api::Response]
497
+ #
498
+ # @raise [Cloudinary::Api::Error]
499
+ def self.reorder_metadata_field_datasource(field_external_id, order_by, direction = nil, options = {})
500
+ uri = [field_external_id, "datasource", "order"]
501
+ params = { :order_by => order_by, :direction => direction }
502
+
503
+ call_metadata_api(:post, uri, params, options)
504
+ end
505
+
489
506
  protected
490
507
 
491
508
  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"])
509
+ cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise('Must supply cloud_name')
510
+ api_key = options[:api_key] || Cloudinary.config.api_key || raise('Must supply api_key')
511
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise('Must supply api_secret')
512
+
513
+ call_cloudinary_api(method, uri, api_key, api_secret, params, options) do |cloudinary, inner_uri|
514
+ [cloudinary, 'v1_1', cloud_name, inner_uri]
528
515
  end
529
516
  end
517
+
530
518
  def self.parse_json_response(response)
531
519
  return Cloudinary::Utils.json_decode(response.body)
532
520
  rescue => e
@@ -593,5 +581,4 @@ class Cloudinary::Api
593
581
  params[by_key] = value
594
582
  call_api("post", "resources/#{resource_type}/#{type}/update_access_mode", params, options)
595
583
  end
596
-
597
584
  end
@@ -33,6 +33,10 @@ module Cloudinary
33
33
  end
34
34
  end
35
35
 
36
+ if url.blank? && acl.blank?
37
+ raise 'AuthToken must contain either an acl or a url property'
38
+ end
39
+
36
40
  token = []
37
41
  token << "ip=#{ip}" if ip
38
42
  token << "st=#{start}" if start
@@ -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