cloudinary 1.17.0 → 1.20.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={})
@@ -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={})
@@ -348,47 +340,156 @@ class Cloudinary::Api
348
340
  call_json_api('GET', json_url, {}, 60, {})
349
341
  end
350
342
 
343
+ # Returns a list of all metadata field definitions.
344
+ #
345
+ # @see https://cloudinary.com/documentation/admin_api#get_metadata_fields Get metadata fields API reference
346
+ #
347
+ # @param [Hash] options Additional options
348
+ # @return [Cloudinary::Api::Response]
349
+ # @raise [Cloudinary::Api::Error]
350
+ def self.list_metadata_fields(options = {})
351
+ call_metadata_api(:get, [], {}, options)
352
+ end
353
+
354
+ # Gets a metadata field by external id.
355
+ #
356
+ # @see https://cloudinary.com/documentation/admin_api#get_a_metadata_field_by_external_id Get metadata field by external ID API reference
357
+ #
358
+ # @param [String] field_external_id The ID of the metadata field to retrieve
359
+ # @param [Hash] options Additional options
360
+ # @return [Cloudinary::Api::Response]
361
+ # @raise [Cloudinary::Api::Error]
362
+ def self.metadata_field_by_field_id(field_external_id, options = {})
363
+ uri = [field_external_id]
364
+
365
+ call_metadata_api(:get, uri, {}, options)
366
+ end
367
+
368
+ # Creates a new metadata field definition.
369
+ #
370
+ # @see https://cloudinary.com/documentation/admin_api#create_a_metadata_field Create metadata field API reference
371
+ #
372
+ # @param [Hash] field The field to add
373
+ # @param [Hash] options Additional options
374
+ # @return [Cloudinary::Api::Response]
375
+ # @raise [Cloudinary::Api::Error]
376
+ def self.add_metadata_field(field, options = {})
377
+ params = only(field, :type, :external_id, :label, :mandatory, :default_value, :validation, :datasource)
378
+
379
+ call_metadata_api(:post, [], params, options)
380
+ end
381
+
382
+ # Updates a metadata field by external id.
383
+ #
384
+ # Updates a metadata field definition (partially, no need to pass the entire object) passed as JSON data.
385
+ # See https://cloudinary.com/documentation/admin_api#generic_structure_of_a_metadata_field for the generic structure
386
+ # of a metadata field.
387
+ #
388
+ # @see https://cloudinary.com/documentation/admin_api#update_a_metadata_field_by_external_id Update metadata field API reference
389
+ #
390
+ # @param [String] field_external_id The id of the metadata field to update
391
+ # @param [Hash] field The field definition
392
+ # @param [Hash] options Additional options
393
+ # @return [Cloudinary::Api::Response]
394
+ # @raise [Cloudinary::Api::Error]
395
+ def self.update_metadata_field(field_external_id, field, options = {})
396
+ uri = [field_external_id]
397
+ params = only(field, :label, :mandatory, :default_value, :validation)
398
+
399
+ call_metadata_api(:put, uri, params, options)
400
+ end
401
+
402
+ # Deletes a metadata field definition.
403
+ #
404
+ # The field should no longer be considered a valid candidate for all other endpoints.
405
+ #
406
+ # @see https://cloudinary.com/documentation/admin_api#delete_a_metadata_field_by_external_id Delete metadata field API reference
407
+ #
408
+ # @param [String] field_external_id The external id of the field to delete
409
+ # @param [Hash] options Additional options
410
+ # @return [Cloudinary::Api::Response] A hash with a "message" key. "ok" value indicates a successful deletion
411
+ # @raise [Cloudinary::Api::Error]
412
+ def self.delete_metadata_field(field_external_id, options = {})
413
+ uri = [field_external_id]
414
+
415
+ call_metadata_api(:delete, uri, {}, options)
416
+ end
417
+
418
+ # Deletes entries in a metadata field datasource.
419
+ #
420
+ # Deletes (blocks) the datasource entries for a specified metadata field definition. Sets the state of the
421
+ # entries to inactive. This is a soft delete, the entries still exist under the hood and can be activated
422
+ # again with the restore datasource entries method.
423
+ #
424
+ # @see https://cloudinary.com/documentation/admin_api#delete_entries_in_a_metadata_field_datasource Delete entries in a metadata field datasource API reference
425
+ #
426
+ # @param [String] field_external_id The id of the field to update
427
+ # @param [Array] entries_external_id The ids of all the entries to delete from the datasource
428
+ # @param [Hash] options Additional options
429
+ # @return [Cloudinary::Api::Response] The remaining datasource entries
430
+ # @raise [Cloudinary::Api::Error]
431
+ def self.delete_datasource_entries(field_external_id, entries_external_id, options = {})
432
+ uri = [field_external_id, "datasource"]
433
+ params = {:external_ids => entries_external_id }
434
+
435
+ call_metadata_api(:delete, uri, params, options)
436
+ end
437
+
438
+ # Updates a metadata field datasource.
439
+ #
440
+ # Updates the datasource of a supported field type (currently only enum and set), passed as JSON data. The
441
+ # update is partial: datasource entries with an existing external_id will be updated and entries with new
442
+ # external_id’s (or without external_id’s) will be appended.
443
+ #
444
+ # @see https://cloudinary.com/documentation/admin_api#update_a_metadata_field_datasource Update a metadata field datasource API reference
445
+ #
446
+ # @param [String] field_external_id The external id of the field to update
447
+ # @param [Array] entries_external_id
448
+ # @param [Hash] options Additional options
449
+ # @return [Cloudinary::Api::Response]
450
+ # @raise [Cloudinary::Api::Error]
451
+ def self.update_metadata_field_datasource(field_external_id, entries_external_id, options = {})
452
+ uri = [field_external_id, "datasource"]
453
+
454
+ params = entries_external_id.each_with_object({:values => [] }) do |item, hash|
455
+ item = only(item, :external_id, :value)
456
+ hash[:values ] << item if item.present?
457
+ end
458
+
459
+ call_metadata_api(:put, uri, params, options)
460
+ end
461
+
462
+ # Restores entries in a metadata field datasource.
463
+ #
464
+ # Restores (unblocks) any previously deleted datasource entries for a specified metadata field definition.
465
+ # Sets the state of the entries to active.
466
+ #
467
+ # @see https://cloudinary.com/documentation/admin_api#restore_entries_in_a_metadata_field_datasource Restore entries in a metadata field datasource API reference
468
+ #
469
+ # @param [String] field_external_id The ID of the metadata field
470
+ # @param [Array] entries_external_ids An array of IDs of datasource entries to restore (unblock)
471
+ # @param [Hash] options Additional options
472
+ # @return [Cloudinary::Api::Response]
473
+ # @raise [Cloudinary::Api::Error]
474
+ def self.restore_metadata_field_datasource(field_external_id, entries_external_ids, options = {})
475
+ uri = [field_external_id, "datasource_restore"]
476
+ params = {:external_ids => entries_external_ids }
477
+
478
+ call_metadata_api(:post, uri, params, options)
479
+ end
480
+
351
481
  protected
352
482
 
353
483
  def self.call_api(method, uri, params, options)
354
- cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || "https://api.cloudinary.com"
355
- cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise("Must supply cloud_name")
356
- api_key = options[:api_key] || Cloudinary.config.api_key || raise("Must supply api_key")
357
- api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
358
- timeout = options[:timeout] || Cloudinary.config.timeout || 60
359
- uri = Cloudinary::Utils.smart_escape(uri)
360
- api_url = [cloudinary, "v1_1", cloud_name, uri].join("/")
361
- # Add authentication
362
- api_url.sub!(%r(^(https?://)), "\\1#{api_key}:#{api_secret}@")
363
-
364
- headers = { "User-Agent" => Cloudinary::USER_AGENT }
365
- if options[:content_type]== :json
366
- payload = params.to_json
367
- headers.merge!("Content-Type"=> 'application/json', "Accept"=> 'application/json')
368
- else
369
- payload = params.reject { |k, v| v.nil? || v=="" }
370
- end
371
- call_json_api(method, api_url, payload, timeout, headers)
372
- end
373
-
374
- def self.call_json_api(method, api_url, payload, timeout, headers)
375
- RestClient::Request.execute(:method => method, :url => api_url, :payload => payload, :timeout => timeout, :headers => headers) do
376
- |response, request, tmpresult|
377
- return Response.new(response) if response.code == 200
378
- exception_class = case response.code
379
- when 400 then BadRequest
380
- when 401 then AuthorizationRequired
381
- when 403 then NotAllowed
382
- when 404 then NotFound
383
- when 409 then AlreadyExists
384
- when 420 then RateLimited
385
- when 500 then GeneralError
386
- else raise GeneralError.new("Server returned unexpected status code - #{response.code} - #{response.body}")
387
- end
388
- json = parse_json_response(response)
389
- raise exception_class.new(json["error"]["message"])
484
+ cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise('Must supply cloud_name')
485
+ api_key = options[:api_key] || Cloudinary.config.api_key || raise('Must supply api_key')
486
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise('Must supply api_secret')
487
+
488
+ call_cloudinary_api(method, uri, api_key, api_secret, params, options) do |cloudinary, inner_uri|
489
+ [cloudinary, 'v1_1', cloud_name, inner_uri]
390
490
  end
391
491
  end
492
+
392
493
  def self.parse_json_response(response)
393
494
  return Cloudinary::Utils.json_decode(response.body)
394
495
  rescue => e
@@ -396,6 +497,22 @@ class Cloudinary::Api
396
497
  raise GeneralError.new("Error parsing server response (#{response.code}) - #{response.body}. Got - #{e}")
397
498
  end
398
499
 
500
+ # Protected function that assists with performing an API call to the metadata_fields part of the Admin API.
501
+ #
502
+ # @protected
503
+ # @param [Symbol] method The HTTP method. Valid methods: get, post, put, delete
504
+ # @param [Array] uri REST endpoint of the API (without 'metadata_fields')
505
+ # @param [Hash] params Query/body parameters passed to the method
506
+ # @param [Hash] options Additional options. Can be an override of the configuration, headers, etc.
507
+ # @return [Cloudinary::Api::Response]
508
+ # @raise [Cloudinary::Api::Error]
509
+ def self.call_metadata_api(method, uri, params, options)
510
+ options[:content_type] = :json
511
+ uri = ["metadata_fields", uri].reject(&:empty?).join("/")
512
+
513
+ call_api(method, uri, params, options)
514
+ end
515
+
399
516
  def self.only(hash, *keys)
400
517
  result = {}
401
518
  keys.each do |key|
@@ -439,5 +556,4 @@ class Cloudinary::Api
439
556
  params[by_key] = value
440
557
  call_api("post", "resources/#{resource_type}/#{type}/update_access_mode", params, options)
441
558
  end
442
-
443
559
  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