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