imagekitio 1.0.10 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +292 -82
  3. data/Rakefile +1 -1
  4. data/lib/active_storage/active_storage.rb +2 -0
  5. data/lib/active_storage/service/ik_file.rb +111 -0
  6. data/lib/active_storage/service/image_kit_io_service.rb +188 -0
  7. data/lib/carrierwave/carrierwave.rb +79 -0
  8. data/lib/carrierwave/storage/ik_file.rb +8 -7
  9. data/lib/carrierwave/storage/imagekit_store.rb +54 -51
  10. data/lib/carrierwave/support/uri_filename.rb +9 -7
  11. data/lib/imagekitio/api_service/bulk.rb +58 -0
  12. data/lib/imagekitio/api_service/custom_metadata_field.rb +52 -0
  13. data/lib/imagekitio/api_service/file.rb +175 -0
  14. data/lib/imagekitio/api_service/folder.rb +49 -0
  15. data/lib/imagekitio/base.rb +12 -0
  16. data/lib/imagekitio/client.rb +186 -0
  17. data/lib/imagekitio/configurable.rb +43 -0
  18. data/lib/imagekitio/constant.rb +36 -0
  19. data/lib/imagekitio/constants/default.rb +22 -0
  20. data/lib/imagekitio/constants/error.rb +69 -0
  21. data/lib/imagekitio/constants/file.rb +11 -0
  22. data/lib/imagekitio/constants/supported_transformation.rb +64 -0
  23. data/lib/imagekitio/constants/url.rb +14 -0
  24. data/lib/imagekitio/errors.rb +4 -0
  25. data/lib/imagekitio/railtie.rb +1 -1
  26. data/lib/imagekitio/request.rb +91 -0
  27. data/lib/imagekitio/sdk/version.rb +5 -0
  28. data/lib/imagekitio/url.rb +241 -0
  29. data/lib/imagekitio/utils/calculation.rb +44 -0
  30. data/lib/imagekitio/utils/formatter.rb +48 -0
  31. data/lib/imagekitio/utils/option_validator.rb +36 -0
  32. data/lib/imagekitio.rb +10 -83
  33. metadata +55 -15
  34. data/lib/imagekit/constants/defaults.rb +0 -20
  35. data/lib/imagekit/constants/errors.rb +0 -77
  36. data/lib/imagekit/constants/file.rb +0 -5
  37. data/lib/imagekit/constants/supported_transformation.rb +0 -57
  38. data/lib/imagekit/constants/url.rb +0 -9
  39. data/lib/imagekit/file.rb +0 -133
  40. data/lib/imagekit/imagekit.rb +0 -117
  41. data/lib/imagekit/resource.rb +0 -56
  42. data/lib/imagekit/sdk/version.rb +0 -5
  43. data/lib/imagekit/url.rb +0 -237
  44. data/lib/imagekit/utils/calculation.rb +0 -36
  45. data/lib/imagekit/utils/formatter.rb +0 -29
@@ -0,0 +1,14 @@
1
+ module ImageKitIo
2
+ module Constants
3
+ module URL
4
+ # Default URL Constants
5
+ BASE_URL = "https://api.imagekit.io/v1/files"
6
+ PURGE_CACHE = "/purge"
7
+ BULK_FILE_DELETE = "/batch/deleteByFileIds"
8
+ UPLOAD = "/upload"
9
+ REMOTE_METADATA_FULL_URL = "https://api.imagekit.io/v1/metadata"
10
+ BULK_BASE_URL = 'https://api.imagekit.io/v1/bulkJobs'
11
+ API_BASE_URL = 'https://api.imagekit.io/v1'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ module ImageKitIo
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -1,4 +1,4 @@
1
- module Imagekitio
1
+ module ImageKitIo
2
2
  class Railtie < ::Rails::Railtie
3
3
  end
4
4
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "rest-client"
5
+ require "json"
6
+ require 'net/http/post/multipart'
7
+ require_relative './constant'
8
+ # Request requests and sends data from server
9
+ module ImageKitIo
10
+ class Request
11
+ include Constantable
12
+
13
+ attr_reader :private_key, :public_key, :url_endpoint, :transformation_position, :options
14
+
15
+ def initialize(private_key, public_key, url_endpoint, transformation_position = nil, options = nil)
16
+ @private_key = private_key
17
+ @public_key = public_key
18
+ @url_endpoint = url_endpoint
19
+ @transformation_position = transformation_position || constants.TRANSFORMATION_POSITION
20
+ @options = options || {}
21
+ end
22
+
23
+ # creates required headers
24
+ def create_headers
25
+ headers = {'Accept-Encoding': "application/json", 'Content-Type': "application/json"}
26
+ headers.update(auth_headers)
27
+ end
28
+
29
+ def auth_headers
30
+ encoded_private_key = Base64.strict_encode64(@private_key+":")
31
+ {Authorization: "Basic #{encoded_private_key}"}
32
+ end
33
+
34
+ # request method communicates with server
35
+ def request(method, url, headers = create_headers, payload = nil)
36
+ headers ||= create_headers
37
+ response = {}
38
+ begin
39
+ if(method.downcase.to_sym == :post)
40
+ uri = URI.parse(url)
41
+ http = Net::HTTP.new(uri.host, uri.port)
42
+ http.use_ssl = (uri.scheme == 'https')
43
+ req = Net::HTTP::Post::Multipart.new uri.path, payload, headers
44
+ resp = http.request(req)
45
+ if resp.code.to_i == 400
46
+ raise RestClient::ExceptionWithResponse, OpenStruct.new(code: 400, body: resp.body)
47
+ end
48
+ else
49
+ resp = RestClient::Request.new(method: method,
50
+ url: url,
51
+ headers: headers,
52
+ payload: payload).execute
53
+ end
54
+ if (resp.code.to_i >= 200) && (resp.code.to_i < 204)
55
+ content_type = resp.respond_to?(:headers) ? resp.headers[:content_type] : resp.content_type
56
+ if (content_type.include? "application/json")
57
+ response[:response] = JSON.parse(resp.body.to_s)
58
+ else
59
+ raise RestClient::ExceptionWithResponse, OpenStruct.new(code: 404, body: resp.body)
60
+ end
61
+ elsif resp.code.to_i == 204
62
+ response[:response] = {'success': true}
63
+ end
64
+
65
+ rescue RestClient::ExceptionWithResponse => err
66
+ response[:error] = if err.http_code.to_i == 404
67
+ {'message': err.response.to_s}
68
+ else
69
+ err.response.is_a?(OpenStruct) ? JSON.parse(err.response.body) : JSON.parse(err.response)
70
+ end
71
+ end
72
+ response
73
+ end
74
+
75
+ def request_stream(method, url, headers: nil, payload: nil, **options, &block)
76
+ headers ||= create_headers
77
+ response = { response: nil, error: nil }
78
+ begin
79
+ RestClient::Request.execute(method: method,
80
+ url: url,
81
+ headers: headers,
82
+ payload: payload,
83
+ **options,
84
+ block_response: block
85
+ )
86
+ rescue RestClient::ExceptionWithResponse => err
87
+ err.http_code == 404 ? response[:error] = {'message': err.response.to_s} : JSON.parse(err.response)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,5 @@
1
+ module ImageKitIo
2
+ module Sdk
3
+ VERSION = '2.1.0'
4
+ end
5
+ end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Url holds url generation method
4
+
5
+ require "cgi"
6
+ require "addressable/uri"
7
+ require "openssl"
8
+ require_relative "./utils/formatter"
9
+ require_relative "sdk/version.rb"
10
+
11
+ module ImageKitIo
12
+ class Url
13
+ include Constantable
14
+
15
+ def initialize(request_obj)
16
+ @req_obj = request_obj
17
+ end
18
+
19
+ def generate_url(options)
20
+ if options.key? :src
21
+ options[:transformation_position] = constants.TRANSFORMATION_POSITION
22
+ end
23
+ extended_options = extend_url_options(options)
24
+ build_url(extended_options)
25
+ end
26
+
27
+ def build_url(options)
28
+ # build url from all options
29
+
30
+ path = options.fetch(:path, "")
31
+ src = options.fetch(:src, "")
32
+ url_endpoint = options.fetch(:url_endpoint, "")
33
+ transformation_position = options[:transformation_position]
34
+
35
+ unless constants.VALID_TRANSFORMATION_POSITION.include? transformation_position
36
+ raise ArgumentError, constants.INVALID_TRANSFORMATION_POS
37
+ end
38
+
39
+ src_param_used_for_url = false
40
+ if (src != "") || (transformation_position == constants.QUERY_TRANSFORMATION_POSITION)
41
+ src_param_used_for_url = true
42
+ end
43
+
44
+ if path == "" && src == ""
45
+ return ""
46
+ end
47
+
48
+ result_url_hash = {'host': "", 'path': "", 'query': ""}
49
+ existing_query=nil
50
+ if path != ""
51
+ parsed_url = Addressable::URI.parse(path)
52
+ existing_query=parsed_url.query
53
+ parsed_host = Addressable::URI.parse(url_endpoint)
54
+ result_url_hash[:scheme] = parsed_host.scheme
55
+
56
+ # making sure single '/' at end
57
+ result_url_hash[:host] = parsed_host.host.to_s.chomp("/") + parsed_host.path.chomp("/") + "/"
58
+ result_url_hash[:path] = trim_slash(parsed_url.path)
59
+ else
60
+ parsed_url = Addressable::URI.parse(src)
61
+ existing_query=parsed_url.query
62
+ host = parsed_url.host
63
+ result_url_hash[:userinfo] = parsed_url.userinfo if parsed_url.userinfo
64
+ result_url_hash[:host] = host
65
+ result_url_hash[:scheme] = parsed_url.scheme
66
+ result_url_hash[:path] = parsed_url.path
67
+ src_param_used_for_url = true
68
+ end
69
+ query_params = {}
70
+ if existing_query!=nil
71
+ existing_query.split("&").each do |part|
72
+ parts=part.split("=")
73
+ if parts.length==2
74
+ query_params[parts[0]]=parts[1]
75
+ end
76
+ end
77
+ end
78
+ options.fetch(:query_parameters, {}).each do |key, value|
79
+ query_params[key]=value
80
+ end
81
+ transformation_str = transformation_to_str(options[:transformation]).chomp("/")
82
+
83
+ unless transformation_str.nil? || transformation_str.strip.empty?
84
+ if (transformation_position == constants.QUERY_TRANSFORMATION_POSITION) || src_param_used_for_url == true
85
+ result_url_hash[:query] = "#{constants.TRANSFORMATION_PARAMETER}=#{transformation_str}"
86
+ query_params[:tr]=transformation_str
87
+ else
88
+ result_url_hash[:path] = "#{constants.TRANSFORMATION_PARAMETER}:#{transformation_str}/#{result_url_hash[:path]}"
89
+ end
90
+
91
+ end
92
+
93
+ result_url_hash[:host] = result_url_hash[:host].to_s.reverse.chomp("/").reverse
94
+ result_url_hash[:path] = result_url_hash[:path].chomp("/")
95
+ result_url_hash[:scheme] ||= "https"
96
+
97
+ query_param_arr = []
98
+ query_param_arr.push("ik-sdk-version=ruby-"+ImageKitIo::Sdk::VERSION)
99
+ query_params.each do |key, value|
100
+ if value.to_s == ""
101
+ query_param_arr.push(key.to_s)
102
+ else
103
+ query_param_arr.push(key.to_s + "=" + value.to_s)
104
+ end
105
+ end
106
+
107
+ query_param_str = query_param_arr.join("&")
108
+ result_url_hash[:query] = query_param_str
109
+
110
+ # Signature String and Timestamp
111
+ # We can do this only for URLs that are created using urlEndpoint and path parameter
112
+ # because we need to know the endpoint to be able to remove it from the URL to create a signature
113
+ # for the remaining. With the src parameter, we would not know the "pattern" in the URL
114
+ if options[:signed] && !(options[:src])
115
+ intermediate_url = result_url_hash.fetch(:scheme, "") + "://" + result_url_hash.fetch(:host, "") + result_url_hash.fetch(:path, "")
116
+ if result_url_hash[:query]!=nil && result_url_hash[:query]!=""
117
+ intermediate_url += result_url_hash.fetch(:query, "")
118
+ end
119
+ end
120
+
121
+ url=hash_to_url(result_url_hash)
122
+ if options[:signed]
123
+ private_key = options[:private_key]
124
+ expire_seconds = options[:expire_seconds]
125
+ expire_timestamp = get_signature_timestamp(expire_seconds)
126
+ url_signature = get_signature(private_key, url, url_endpoint, expire_timestamp)
127
+ query_param_arr.push(constants.SIGNATURE_PARAMETER + "=" + url_signature)
128
+
129
+ if expire_timestamp && (expire_timestamp != constants.TIMESTAMP)
130
+ query_param_arr.push(constants.TIMESTAMP_PARAMETER + "=" + expire_timestamp.to_s)
131
+ end
132
+
133
+ query_param_str = query_param_arr.join("&")
134
+ result_url_hash[:query] = query_param_str
135
+
136
+ url=hash_to_url(result_url_hash)
137
+ end
138
+ url
139
+ end
140
+
141
+ def transformation_to_str(transformation)
142
+ # creates transformation_position string for url
143
+ # from transformation dictionary
144
+
145
+ unless transformation.is_a?(Array)
146
+ return ""
147
+ end
148
+
149
+ parsed_transforms = []
150
+ (0..(transformation.length - 1)).each do |i|
151
+ parsed_transform_step = []
152
+
153
+ transformation[i].keys.each do |key|
154
+ transform_key = constants.SUPPORTED_TRANS.fetch(key, nil)
155
+ transform_key ||= key
156
+
157
+ if transform_key == "oi" || transform_key == "di"
158
+ transformation[i][key][0] = "" if transformation[i][key][0] == "/"
159
+ transformation[i][key] = transformation[i][key].gsub("/", "@@")
160
+ end
161
+
162
+ if transformation[i][key] == "-"
163
+ parsed_transform_step.push(transform_key)
164
+ elsif transform_key == 'raw'
165
+ parsed_transform_step.push(transformation[i][key])
166
+ else
167
+ parsed_transform_step.push("#{transform_key}#{constants.TRANSFORM_KEY_VALUE_DELIMITER}#{transformation[i][key]}")
168
+ end
169
+ end
170
+ parsed_transforms.push(parsed_transform_step.join(constants.TRANSFORM_DELIMITER))
171
+ end
172
+ parsed_transforms.join(constants.CHAIN_TRANSFORM_DELIMITER)
173
+ end
174
+
175
+ def get_signature_timestamp(seconds)
176
+ # this function returns either default time stamp
177
+ # or current unix time and expiry seconds to get
178
+ # signature time stamp
179
+
180
+ if seconds.to_i == 0
181
+ constants.DEFAULT_TIMESTAMP
182
+ else
183
+ DateTime.now.strftime("%s").to_i + seconds.to_i
184
+ end
185
+ end
186
+
187
+ def get_signature(private_key, url, url_endpoint, expiry_timestamp)
188
+ # creates signature(hashed hex key) and returns from
189
+ # private_key, url, url_endpoint and expiry_timestamp
190
+ if expiry_timestamp==0
191
+ expiry_timestamp=constants.DEFAULT_TIMESTAMP
192
+ end
193
+ if url_endpoint[url_endpoint.length-1]!="/"
194
+ url_endpoint+="/"
195
+ end
196
+ replaced_url=url.gsub(url_endpoint, "")
197
+ replaced_url = replaced_url + expiry_timestamp.to_s
198
+ OpenSSL::HMAC.hexdigest("SHA1", private_key, replaced_url)
199
+ end
200
+
201
+ def extend_url_options(options)
202
+ attr_dict = {"public_key": @req_obj.public_key,
203
+ "private_key": @req_obj.private_key,
204
+ "url_endpoint": @req_obj.url_endpoint,
205
+ "transformation_position": @req_obj.transformation_position, }
206
+ # extending url options
207
+ attr_dict.merge(options)
208
+ end
209
+
210
+ def hash_to_url(url_hash)
211
+ generated_url = url_hash.fetch(:scheme, "") + "://" + url_hash.fetch(:host, "") + url_hash.fetch(:path, "")
212
+ if url_hash[:query] != ""
213
+ generated_url = generated_url + "?" + url_hash.fetch(:query, "")
214
+ return generated_url
215
+ end
216
+ generated_url
217
+ end
218
+
219
+ def trim_slash(str, both = true)
220
+ if str == ""
221
+ return ""
222
+ end
223
+ # remove slash from a string
224
+ # if both is not provide trims both slash
225
+ # example - '/abc/' returns 'abc'
226
+ # if both=false it will only trim end slash
227
+ # example - '/abc/' returns '/abc'
228
+ # NOTE: IT'S RECOMMENDED TO USE inbuilt .chomp('string you want to remove')
229
+ # FOR REMOVING ONLY TRAILING SLASh
230
+ if both
231
+ str[0].chomp("/") + str[1..-2] + str[-1].chomp("/")
232
+ else
233
+ str.chomp("/")
234
+ end
235
+ end
236
+
237
+ # class Imagekit
238
+
239
+ # end
240
+ end
241
+ end
@@ -0,0 +1,44 @@
1
+ require "date"
2
+ require "securerandom"
3
+
4
+ module ImageKitIo
5
+ module Utils
6
+ module Calculation
7
+ DEFAULT_TIME_DIFF = 60 * 30
8
+
9
+ module_function
10
+
11
+ def is_valid_hex(hex_string)
12
+ # checks if hexadecimal value is valid or not
13
+ /^[[:xdigit:]]+$/ === hex_string
14
+ end
15
+
16
+ def hamming_distance(first, second)
17
+ # Calculate Hamming distance between to hex string
18
+ unless is_valid_hex(first) && is_valid_hex(second)
19
+ raise ArgumentError, "Both argument should be hexadecimal"
20
+ end
21
+ a = first.to_i(16)
22
+ b = second.to_i(16)
23
+ (a ^ b).to_s(2).count("1")
24
+ end
25
+
26
+ def get_authenticated_params(token, expire, private_key)
27
+ # return authenticated param
28
+ default_expire = DateTime.now.strftime("%s").to_i + DEFAULT_TIME_DIFF
29
+ token ||= SecureRandom.uuid
30
+
31
+ auth_params = {'token': token, 'expire': expire, 'signature': ""}
32
+ unless private_key
33
+ return nil
34
+ end
35
+
36
+ signature = OpenSSL::HMAC.hexdigest("SHA1", private_key, token.to_s + expire.to_s)
37
+ auth_params[:token] = token
38
+ auth_params[:expire] = expire || default_expire
39
+ auth_params[:signature] = signature
40
+ auth_params
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ require 'json'
2
+
3
+ module ImageKitIo
4
+ module Utils
5
+ module Formatter
6
+
7
+ module_function
8
+ def snake_to_camel(word)
9
+ word_list = word.split("_")
10
+ result = []
11
+ word_list&.each { |i|
12
+ if i == word_list[0]
13
+ result.push(i)
14
+ else
15
+ result.push(i.capitalize)
16
+ end
17
+ }
18
+ result.join
19
+ end
20
+
21
+ def camel_to_snake(camel_word)
22
+ # convert camel case to snake case
23
+ camel_word.to_s.gsub(/::/, "/")
24
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
25
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
26
+ .tr("-", "_")
27
+ .downcase
28
+ end
29
+
30
+ def request_formatter(data)
31
+ result = {}
32
+ data.each do |key, val|
33
+ result[snake_to_camel(key.to_s)] = val
34
+ end
35
+ result
36
+ end
37
+
38
+ def format_to_json(options, key, expected_class)
39
+ options ||= {}
40
+ val = options[key]
41
+ if !val.nil? && val.is_a?(expected_class)
42
+ options[key] = options[key].to_json
43
+ end
44
+ options
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ require_relative './formatter'
2
+
3
+ module ImageKitIo
4
+ module Utils
5
+ module OptionValidator
6
+ include Formatter
7
+ include Constantable
8
+
9
+ module_function
10
+
11
+ def validate_upload_options(options)
12
+
13
+ # Validates upload value, checks if params are valid,
14
+ # changes snake to camel case which is supported by
15
+ # ImageKitIo server
16
+
17
+
18
+ response_list = []
19
+ options.each do |key, val|
20
+ if constants.VALID_UPLOAD_OPTIONS.include?(key.to_s)
21
+ if val.is_a?(Array)
22
+ val = val.join(",")
23
+ end
24
+ if val.is_a?(TrueClass) || val.is_a?(FalseClass)
25
+ val = val.to_s
26
+ end
27
+ options[key] = val
28
+ else
29
+ return false
30
+ end
31
+ end
32
+ request_formatter(options)
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/imagekitio.rb CHANGED
@@ -1,86 +1,13 @@
1
- require "imagekitio/railtie"
1
+ require "imagekitio/railtie" if defined? Rails
2
2
 
3
- require 'carrierwave'
4
3
  require 'base64'
5
- require_relative './carrierwave/storage/imagekit_store'
6
- require_relative './carrierwave/storage/ik_file'
7
- require_relative './carrierwave/support/uri_filename'
8
- require_relative './imagekit/imagekit.rb'
9
- require_relative "./imagekit/resource"
10
- require_relative "./imagekit/file"
11
- require_relative "./imagekit/url"
12
- require_relative "./imagekit/utils/calculation"
13
4
 
14
- module CarrierWave
15
- module Uploader
16
- class Base
17
-
18
- def initialize(*)
19
- ik_config=Rails.application.config.imagekit
20
- @imagekit=ImageKit::ImageKitClient.new(ik_config[:private_key],ik_config[:public_key],ik_config[:url_endpoint])
21
- @options={}
22
- end
23
-
24
- configure do |config|
25
- config.storage_engines[:imagekit_store] = 'CarrierWave::Storage::ImageKitStore'
26
- end
27
-
28
- def filename
29
- if options!=nil
30
- @options=options
31
- end
32
- folder=nil
33
- begin
34
- folder=store_dir
35
- rescue
36
- end
37
-
38
- if folder!=nil
39
- @options[:folder]=folder
40
- end
41
-
42
- if self.file!=nil
43
- base64=Base64.encode64(::File.open(self.file.file, "rb").read)
44
- resp=@imagekit.upload_file(open(self.file.file,'rb'),self.file.filename,@options)
45
- # ::File.delete(self.file.file)
46
- res=resp[:response].to_json
47
- if res!="null"
48
- res
49
- else
50
- "{\"filePath\":\"\",\"url\":\"\",\"name\":\"\"}"
51
- end
52
- else
53
- "{\"filePath\":\"\",\"url\":\"\",\"name\":\"\"}"
54
- end
55
- end
56
-
57
- def fileId
58
- JSON.parse(self.identifier)['fileId']
59
- end
60
-
61
- def blob
62
- JSON.parse(self.identifier)
63
- end
64
-
65
- def url_with(opt)
66
- path=JSON.parse(self.identifier)['filePath']
67
- opt[:path]=path
68
- url=@imagekit.url(opt)
69
- end
70
-
71
- def url
72
- JSON.parse(self.identifier)['url']
73
- end
74
-
75
- def options
76
- options={}
77
- end
78
-
79
- def store_dir
80
- store_dir=nil
81
- end
82
- end
83
-
84
- end
85
-
86
- end
5
+ require_relative './imagekitio/constant'
6
+ require_relative './imagekitio/base'
7
+ require_relative './imagekitio/configurable'
8
+ require_relative './imagekitio/client'
9
+ require_relative "./imagekitio/request"
10
+ require_relative "./imagekitio/url"
11
+ require_relative './imagekitio/api_service/custom_metadata_field'
12
+ require_relative './imagekitio/api_service/file'
13
+ require_relative './imagekitio/api_service/folder'