imagekitio 1.0.3
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.
- checksums.yaml +7 -0
- data/README.md +517 -0
- data/Rakefile +27 -0
- data/lib/carrierwave/storage/ik_file.rb +50 -0
- data/lib/carrierwave/storage/imagekit_store.rb +68 -0
- data/lib/carrierwave/support/uri_filename.rb +10 -0
- data/lib/imagekit/constants/defaults.rb +20 -0
- data/lib/imagekit/constants/errors.rb +71 -0
- data/lib/imagekit/constants/file.rb +5 -0
- data/lib/imagekit/constants/supported_transformation.rb +44 -0
- data/lib/imagekit/constants/url.rb +9 -0
- data/lib/imagekit/file.rb +133 -0
- data/lib/imagekit/imagekit.rb +108 -0
- data/lib/imagekit/resource.rb +55 -0
- data/lib/imagekit/sdk/version.rb +5 -0
- data/lib/imagekit/url.rb +228 -0
- data/lib/imagekit/utils/calculation.rb +36 -0
- data/lib/imagekit/utils/formatter.rb +29 -0
- data/lib/imagekitio.rb +72 -0
- data/lib/imagekitio/railtie.rb +4 -0
- data/lib/tasks/imagekitio/imagekitio_tasks.rake +4 -0
- metadata +124 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$VERBOSE = nil
|
4
|
+
|
5
|
+
require_relative "./resource"
|
6
|
+
require_relative "./file"
|
7
|
+
require_relative "./url"
|
8
|
+
require_relative "./utils/calculation"
|
9
|
+
|
10
|
+
module ImageKit
|
11
|
+
class Error < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
# ImageKit class holds each method will be used by user
|
15
|
+
class ImageKitClient
|
16
|
+
attr_reader :file
|
17
|
+
|
18
|
+
def initialize(private_key, public_key, url_endpoint, transformation_pos = nil, options = nil)
|
19
|
+
@private_key = private_key
|
20
|
+
@public_key = public_key
|
21
|
+
@url_endpoint = url_endpoint
|
22
|
+
@transformation_position = transformation_pos
|
23
|
+
@options = options
|
24
|
+
|
25
|
+
@ik_req = ImageKitRequest.new(private_key, public_key, url_endpoint)
|
26
|
+
@file = ImageKitFile.new(@ik_req)
|
27
|
+
@url_obj = Url.new(@ik_req)
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_ik_request(ik_req)
|
32
|
+
# setter for imagekit request mainly will be used for
|
33
|
+
# test
|
34
|
+
@ik_req = ik_req
|
35
|
+
end
|
36
|
+
|
37
|
+
def url(options)
|
38
|
+
@url_obj.generate_url(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def upload_file(file = nil, file_name = nil, options = nil)
|
42
|
+
# upload file to imagekit server
|
43
|
+
@file.upload(file, file_name, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def list_files(options)
|
47
|
+
# list all files
|
48
|
+
@file.list(options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_file_details(file_identifier)
|
52
|
+
# Get file detail by file-id or file_url
|
53
|
+
@file.details(file_identifier)
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_file_details(file_id, options)
|
57
|
+
# update file details by file id and other options payload
|
58
|
+
@file.update_details(file_id, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete_file(file_id)
|
62
|
+
# Delete a file by file-id
|
63
|
+
@file.delete(file_id)
|
64
|
+
end
|
65
|
+
|
66
|
+
def bulk_file_delete(file_ids)
|
67
|
+
# Delete file in bulks by list of file id
|
68
|
+
@file.batch_delete(file_ids)
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_file_metadata(file_id)
|
72
|
+
# Get metadata of a file by file-id
|
73
|
+
@file.get_metadata(file_id)
|
74
|
+
end
|
75
|
+
|
76
|
+
def purge_file_cache(file_url)
|
77
|
+
# Purge cache from ImageKit server by file_url
|
78
|
+
@file.purge_cache(file_url)
|
79
|
+
end
|
80
|
+
|
81
|
+
def purge_file_cache_status(request_id)
|
82
|
+
@file.purge_cache_status(request_id.to_s)
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_remote_file_url_metadata(remote_file_url = "")
|
86
|
+
@file.get_metadata_from_remote_url(remote_file_url)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Get metadata from remote_file_url
|
90
|
+
# param remote_file_url: url string of remote file
|
91
|
+
|
92
|
+
def phash_distance(first, second)
|
93
|
+
# Get hamming distance between two phash(image hash) to check
|
94
|
+
# similarity between images
|
95
|
+
|
96
|
+
unless first && second
|
97
|
+
raise ArgumentError, Error::MISSING_PHASH_VALUE
|
98
|
+
end
|
99
|
+
hamming_distance(first, second)
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_authentication_parameters(token = nil, expire = nil)
|
103
|
+
# Get Authentication params
|
104
|
+
get_authenticated_params(token, expire, @ik_req.private_key)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
require "rest-client"
|
5
|
+
require "json"
|
6
|
+
require_relative "./constants/defaults"
|
7
|
+
|
8
|
+
# ImageKitRequest requests and sends data from server
|
9
|
+
class ImageKitRequest
|
10
|
+
attr_reader :private_key, :public_key, :url_endpoint, :transformation_position, :options
|
11
|
+
|
12
|
+
def initialize(private_key, public_key, url_endpoint, transformation_position = nil, options = nil)
|
13
|
+
@private_key = private_key
|
14
|
+
@public_key = public_key
|
15
|
+
@url_endpoint = url_endpoint
|
16
|
+
@transformation_position = transformation_position || Default::TRANSFORMATION_POSITION
|
17
|
+
@options = options || {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# creates required headers
|
21
|
+
def create_headers
|
22
|
+
headers = {'Accept-Encoding': "application/json", 'Content-Type': "application/json"}
|
23
|
+
headers.update(auth_headers)
|
24
|
+
end
|
25
|
+
|
26
|
+
def auth_headers
|
27
|
+
encoded_private_key = Base64.strict_encode64(@private_key+":")
|
28
|
+
{Authorization: "Basic #{encoded_private_key}"}
|
29
|
+
end
|
30
|
+
|
31
|
+
# request method communicates with server
|
32
|
+
def request(method, url, headers = nil, payload = nil)
|
33
|
+
headers ||= create_headers
|
34
|
+
response = {response: nil, error: nil}
|
35
|
+
begin
|
36
|
+
resp = RestClient::Request.new(method: method,
|
37
|
+
url: url,
|
38
|
+
headers: headers,
|
39
|
+
payload: payload).execute
|
40
|
+
|
41
|
+
|
42
|
+
if resp.code == 404
|
43
|
+
raise RestClient::ExceptionWithResponse
|
44
|
+
elsif (resp.code >= 200) && (resp.code < 204)
|
45
|
+
response[:response] = JSON.parse(resp.body.to_s)
|
46
|
+
elsif resp.code == 204
|
47
|
+
response[:response] = {'success': true}
|
48
|
+
end
|
49
|
+
|
50
|
+
rescue RestClient::ExceptionWithResponse => err
|
51
|
+
err.http_code == 404 ? response[:error] = {'message': err.response.to_s} : JSON.parse(err.response)
|
52
|
+
end
|
53
|
+
response
|
54
|
+
end
|
55
|
+
end
|
data/lib/imagekit/url.rb
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Url holds url generation method
|
4
|
+
|
5
|
+
require "cgi"
|
6
|
+
require "openssl"
|
7
|
+
require_relative "./utils/formatter"
|
8
|
+
require_relative "./constants/defaults"
|
9
|
+
require_relative "./constants/supported_transformation"
|
10
|
+
require_relative "./sdk/version.rb"
|
11
|
+
|
12
|
+
class Url
|
13
|
+
def initialize(request_obj)
|
14
|
+
@req_obj = request_obj
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_url(options)
|
18
|
+
if options.key? :src
|
19
|
+
options[:transformation_position] = Default::TRANSFORMATION_POSITION
|
20
|
+
end
|
21
|
+
extended_options = extend_url_options(options)
|
22
|
+
build_url(extended_options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_url(options)
|
26
|
+
# build url from all options
|
27
|
+
|
28
|
+
path = options.fetch(:path, "")
|
29
|
+
src = options.fetch(:src, "")
|
30
|
+
url_endpoint = options.fetch(:url_endpoint, "")
|
31
|
+
transformation_position = options[:transformation_position]
|
32
|
+
|
33
|
+
unless Default::VALID_TRANSFORMATION_POSITION.include? transformation_position
|
34
|
+
raise ArgumentError, INVALID_TRANSFORMATION_POS
|
35
|
+
end
|
36
|
+
|
37
|
+
src_param_used_for_url = false
|
38
|
+
if (src != "") || (transformation_position == Default::QUERY_TRANSFORMATION_POSITION)
|
39
|
+
src_param_used_for_url = true
|
40
|
+
end
|
41
|
+
|
42
|
+
if path == "" && src == ""
|
43
|
+
return ""
|
44
|
+
end
|
45
|
+
|
46
|
+
result_url_hash = {'host': "", 'path': "", 'query': ""}
|
47
|
+
existing_query=nil
|
48
|
+
if path != ""
|
49
|
+
parsed_url = URI.parse(path)
|
50
|
+
existing_query=parsed_url.query
|
51
|
+
parsed_host = URI(url_endpoint)
|
52
|
+
result_url_hash[:scheme] = parsed_host.scheme
|
53
|
+
|
54
|
+
# making sure single '/' at end
|
55
|
+
result_url_hash[:host] = parsed_host.host.to_s.chomp("/") + parsed_host.path.chomp("/") + "/"
|
56
|
+
result_url_hash[:path] = trim_slash(parsed_url.path)
|
57
|
+
else
|
58
|
+
parsed_url = URI.parse(src)
|
59
|
+
existing_query=parsed_url.query
|
60
|
+
host = parsed_url.host
|
61
|
+
result_url_hash[:userinfo] = parsed_url.userinfo if parsed_url.userinfo
|
62
|
+
result_url_hash[:host] = host
|
63
|
+
result_url_hash[:scheme] = parsed_url.scheme
|
64
|
+
result_url_hash[:path] = parsed_url.path
|
65
|
+
src_param_used_for_url = true
|
66
|
+
end
|
67
|
+
query_params = {}
|
68
|
+
if existing_query!=nil
|
69
|
+
existing_query.split("&").each do |part|
|
70
|
+
parts=part.split("=")
|
71
|
+
if parts.length==2
|
72
|
+
query_params[parts[0]]=parts[1]
|
73
|
+
else
|
74
|
+
query_params[parts[0]]=""
|
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
|
+
if transformation_str
|
84
|
+
if (transformation_position == Default::QUERY_TRANSFORMATION_POSITION) || src_param_used_for_url == true
|
85
|
+
result_url_hash[:query] = "#{Default::TRANSFORMATION_PARAMETER}=#{transformation_str}"
|
86
|
+
query_params[:tr]=transformation_str
|
87
|
+
else
|
88
|
+
result_url_hash[:path] = "#{Default::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
|
+
|
98
|
+
# Signature String and Timestamp
|
99
|
+
# We can do this only for URLs that are created using urlEndpoint and path parameter
|
100
|
+
# because we need to know the endpoint to be able to remove it from the URL to create a signature
|
101
|
+
# for the remaining. With the src parameter, we would not know the "pattern" in the URL
|
102
|
+
query_param_arr = []
|
103
|
+
query_param_arr.push("ik-sdk-version=ruby-"+Imagekit::Sdk::VERSION)
|
104
|
+
if options[:signed] && !(options[:src])
|
105
|
+
intermediate_url = result_url_hash.fetch(:scheme, "") + "://" + result_url_hash.fetch(:host, "") + result_url_hash.fetch(:path, "")
|
106
|
+
if result_url_hash[:query]!=nil && result_url_hash[:query]!=""
|
107
|
+
intermediate_url += result_url_hash.fetch(:query, "")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
query_params.each do |key, value|
|
111
|
+
query_param_arr.push(key.to_s + "=" + value.to_s)
|
112
|
+
end
|
113
|
+
|
114
|
+
query_param_str = query_param_arr.join("&")
|
115
|
+
result_url_hash[:query] = query_param_str
|
116
|
+
url=hash_to_url(result_url_hash)
|
117
|
+
if options[:signed]
|
118
|
+
private_key = options[:private_key]
|
119
|
+
expire_seconds = options[:expire_seconds]
|
120
|
+
expire_timestamp = get_signature_timestamp(expire_seconds)
|
121
|
+
url_signature = get_signature(private_key, url, url_endpoint, expire_timestamp)
|
122
|
+
query_param_arr.push(Default::SIGNATURE_PARAMETER + "=" + url_signature)
|
123
|
+
|
124
|
+
if expire_timestamp && (expire_timestamp != Default::TIMESTAMP)
|
125
|
+
query_param_arr.push(Default::TIMESTAMP_PARAMETER + "=" + expire_timestamp.to_s)
|
126
|
+
end
|
127
|
+
|
128
|
+
query_param_str = query_param_arr.join("&")
|
129
|
+
result_url_hash[:query] = query_param_str
|
130
|
+
|
131
|
+
url=hash_to_url(result_url_hash)
|
132
|
+
end
|
133
|
+
url
|
134
|
+
end
|
135
|
+
|
136
|
+
def transformation_to_str(transformation)
|
137
|
+
# creates transformation_position string for url
|
138
|
+
# from transformation dictionary
|
139
|
+
|
140
|
+
unless transformation.is_a?(Array)
|
141
|
+
return ""
|
142
|
+
end
|
143
|
+
|
144
|
+
parsed_transforms = []
|
145
|
+
(0..(transformation.length - 1)).each do |i|
|
146
|
+
parsed_transform_step = []
|
147
|
+
|
148
|
+
transformation[i].keys.each do |key|
|
149
|
+
transform_key = SUPPORTED_TRANS.fetch(key, nil)
|
150
|
+
transform_key ||= key
|
151
|
+
|
152
|
+
if transformation[i][key] == "-"
|
153
|
+
parsed_transform_step.push(transform_key)
|
154
|
+
else
|
155
|
+
parsed_transform_step.push("#{transform_key}#{Default::TRANSFORM_KEY_VALUE_DELIMITER}#{transformation[i][key]}")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
parsed_transforms.push(parsed_transform_step.join(Default::TRANSFORM_DELIMITER))
|
159
|
+
end
|
160
|
+
parsed_transforms.join(Default::CHAIN_TRANSFORM_DELIMITER)
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_signature_timestamp(seconds)
|
164
|
+
# this function returns either default time stamp
|
165
|
+
# or current unix time and expiry seconds to get
|
166
|
+
# signature time stamp
|
167
|
+
|
168
|
+
if seconds.to_i == 0
|
169
|
+
Default::DEFAULT_TIMESTAMP
|
170
|
+
else
|
171
|
+
DateTime.now.strftime("%s").to_i + seconds.to_i
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def get_signature(private_key, url, url_endpoint, expiry_timestamp)
|
176
|
+
# creates signature(hashed hex key) and returns from
|
177
|
+
# private_key, url, url_endpoint and expiry_timestamp
|
178
|
+
if expiry_timestamp==0
|
179
|
+
expiry_timestamp=Default::DEFAULT_TIMESTAMP
|
180
|
+
end
|
181
|
+
if url_endpoint[url_endpoint.length-1]!="/"
|
182
|
+
url_endpoint+="/"
|
183
|
+
end
|
184
|
+
replaced_url=url.gsub(url_endpoint, "")
|
185
|
+
replaced_url = replaced_url + expiry_timestamp.to_s
|
186
|
+
OpenSSL::HMAC.hexdigest("SHA1", private_key, replaced_url)
|
187
|
+
end
|
188
|
+
|
189
|
+
def extend_url_options(options)
|
190
|
+
attr_dict = {"public_key": @req_obj.public_key,
|
191
|
+
"private_key": @req_obj.private_key,
|
192
|
+
"url_endpoint": @req_obj.url_endpoint,
|
193
|
+
"transformation_position": @req_obj.transformation_position, }
|
194
|
+
# extending url options
|
195
|
+
attr_dict.merge(options)
|
196
|
+
end
|
197
|
+
|
198
|
+
def hash_to_url(url_hash)
|
199
|
+
generated_url = url_hash.fetch(:scheme, "") + "://" + url_hash.fetch(:host, "") + url_hash.fetch(:path, "")
|
200
|
+
if url_hash[:query] != ""
|
201
|
+
generated_url = generated_url + "?" + url_hash.fetch(:query, "")
|
202
|
+
return generated_url
|
203
|
+
end
|
204
|
+
generated_url
|
205
|
+
end
|
206
|
+
|
207
|
+
def trim_slash(str, both = true)
|
208
|
+
if str == ""
|
209
|
+
return ""
|
210
|
+
end
|
211
|
+
# remove slash from a string
|
212
|
+
# if both is not provide trims both slash
|
213
|
+
# example - '/abc/' returns 'abc'
|
214
|
+
# if both=false it will only trim end slash
|
215
|
+
# example - '/abc/' returns '/abc'
|
216
|
+
# NOTE: IT'S RECOMMENDED TO USE inbuilt .chomp('string you want to remove')
|
217
|
+
# FOR REMOVING ONLY TRAILING SLASh
|
218
|
+
if both
|
219
|
+
str[0].chomp("/") + str[1..-2] + str[-1].chomp("/")
|
220
|
+
else
|
221
|
+
str.chomp("/")
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# class Imagekit
|
226
|
+
|
227
|
+
# end
|
228
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "date"
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
DEFAULT_TIME_DIFF = 60 * 30
|
5
|
+
|
6
|
+
def is_valid_hex(hex_string)
|
7
|
+
# checks if hexadecimal value is valid or not
|
8
|
+
/^[[:xdigit:]]+$/ === hex_string
|
9
|
+
end
|
10
|
+
|
11
|
+
def hamming_distance(first, second)
|
12
|
+
# Calculate Hamming distance between to hex string
|
13
|
+
unless is_valid_hex(first) && is_valid_hex(second)
|
14
|
+
raise ArgumentError, "Both argument should be hexadecimal"
|
15
|
+
end
|
16
|
+
a = first.to_i(16)
|
17
|
+
b = second.to_i(16)
|
18
|
+
(a ^ b).to_s(2).count("1")
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_authenticated_params(token, expire, private_key)
|
22
|
+
# return authenticated param
|
23
|
+
default_expire = DateTime.now.strftime("%s").to_i + DEFAULT_TIME_DIFF
|
24
|
+
token ||= SecureRandom.uuid
|
25
|
+
|
26
|
+
auth_params = {'token': token, 'expire': expire, 'signature': ""}
|
27
|
+
unless private_key
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
|
31
|
+
signature = OpenSSL::HMAC.hexdigest("SHA1", private_key, token.to_s + expire.to_s)
|
32
|
+
auth_params[:token] = token
|
33
|
+
auth_params[:expire] = expire || default_expire
|
34
|
+
auth_params[:signature] = signature
|
35
|
+
auth_params
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
def snake_to_camel(word)
|
2
|
+
word_list = word.split("_")
|
3
|
+
result = []
|
4
|
+
word_list&.each { |i|
|
5
|
+
if i == word_list[0]
|
6
|
+
result.push(i)
|
7
|
+
else
|
8
|
+
result.push(i.capitalize)
|
9
|
+
end
|
10
|
+
}
|
11
|
+
result.join
|
12
|
+
end
|
13
|
+
|
14
|
+
def camel_to_snake(camel_word)
|
15
|
+
# convert camel case to snake case
|
16
|
+
camel_word.to_s.gsub(/::/, "/")
|
17
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
18
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
19
|
+
.tr("-", "_")
|
20
|
+
.downcase
|
21
|
+
end
|
22
|
+
|
23
|
+
def request_formatter(data)
|
24
|
+
result = {}
|
25
|
+
data.each do |key, val|
|
26
|
+
result[snake_to_camel(key.to_s)] = val
|
27
|
+
end
|
28
|
+
result
|
29
|
+
end
|