cloudinary 1.17.0 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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