uploadcare-ruby 1.0.6 → 1.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/LICENSE +1 -1
- data/README.md +343 -169
- data/lib/uploadcare.rb +1 -0
- data/lib/uploadcare/api.rb +2 -1
- data/lib/uploadcare/resources/file.rb +27 -3
- data/lib/uploadcare/rest/auth/auth.rb +31 -0
- data/lib/uploadcare/rest/auth/secure.rb +43 -0
- data/lib/uploadcare/rest/auth/simple.rb +16 -0
- data/lib/uploadcare/rest/connections/api_connection.rb +19 -7
- data/lib/uploadcare/rest/connections/upload_connection.rb +2 -2
- data/lib/uploadcare/rest/middlewares/auth_middleware.rb +24 -0
- data/lib/uploadcare/rest/middlewares/parse_json_middleware.rb +1 -1
- data/lib/uploadcare/rest/middlewares/raise_error_middleware.rb +2 -2
- data/lib/uploadcare/version.rb +1 -1
- data/spec/resources/file_spec.rb +107 -7
- data/spec/rest/api_connection_spec.rb +58 -9
- data/spec/rest/auth/secure_spec.rb +66 -0
- data/spec/rest/auth/simple_spec.rb +31 -0
- data/spec/spec_helper.rb +18 -1
- metadata +33 -25
data/lib/uploadcare.rb
CHANGED
data/lib/uploadcare/api.rb
CHANGED
@@ -4,6 +4,7 @@ Dir[File.dirname(__FILE__) + '/utils/*.rb'].each {|file| require file }
|
|
4
4
|
Dir[File.dirname(__FILE__) + '/errors/*.rb'].each {|file| require file }
|
5
5
|
Dir[File.dirname(__FILE__) + '/rest/middlewares/*.rb'].each {|file| require file }
|
6
6
|
Dir[File.dirname(__FILE__) + '/rest/connections/*.rb'].each {|file| require file }
|
7
|
+
Dir[File.dirname(__FILE__) + '/rest/auth/*.rb'].sort.each {|file| require file }
|
7
8
|
Dir[File.dirname(__FILE__) + '/api/*.rb'].each {|file| require file }
|
8
9
|
Dir[File.dirname(__FILE__) + '/resources/*.rb'].each {|file| require file }
|
9
10
|
|
@@ -11,7 +12,7 @@ Dir[File.dirname(__FILE__) + '/resources/*.rb'].each {|file| require file }
|
|
11
12
|
module Uploadcare
|
12
13
|
class Api
|
13
14
|
attr_reader :options
|
14
|
-
|
15
|
+
|
15
16
|
include Uploadcare::RawApi
|
16
17
|
include Uploadcare::UploadingApi
|
17
18
|
include Uploadcare::FileApi
|
@@ -5,7 +5,7 @@ module Uploadcare
|
|
5
5
|
class File < OpenStruct
|
6
6
|
def initialize api, uuid_or_cdn_url, data=nil
|
7
7
|
result = Uploadcare::Parser.parse_file_string uuid_or_cdn_url
|
8
|
-
|
8
|
+
|
9
9
|
file = {uuid: result.uuid, operations: result.operations}
|
10
10
|
|
11
11
|
@api = api
|
@@ -65,7 +65,7 @@ module Uploadcare
|
|
65
65
|
def store
|
66
66
|
data = @api.put "/files/#{uuid}/storage/"
|
67
67
|
set_data data
|
68
|
-
self
|
68
|
+
self
|
69
69
|
end
|
70
70
|
|
71
71
|
# nil is returning if there is no way to say for sure
|
@@ -95,6 +95,8 @@ module Uploadcare
|
|
95
95
|
# copy file to target location
|
96
96
|
# note what file copied with operations
|
97
97
|
def copy with_operations=true, target=nil
|
98
|
+
warn "[DEPRECATION] `copy` is deprecated and will be removed in "\
|
99
|
+
"version 3.0. Please use `internal_copy` or `external_copy` instead."
|
98
100
|
data = Hash.new
|
99
101
|
data[:target] = target if target
|
100
102
|
data[:source] = self.cdn_url_with_operations if with_operations
|
@@ -103,6 +105,28 @@ module Uploadcare
|
|
103
105
|
@api.post "/files/", data
|
104
106
|
end
|
105
107
|
|
108
|
+
# Create a copy of the file in a default storage
|
109
|
+
def internal_copy(options={})
|
110
|
+
data = {
|
111
|
+
source: cdn_url(!options.fetch(:strip_operations){ false }),
|
112
|
+
store: options.fetch(:store){ nil }
|
113
|
+
}.reject{|_,v| v.nil?}
|
114
|
+
|
115
|
+
@api.post "/files/", data
|
116
|
+
end
|
117
|
+
|
118
|
+
# Copy file to a custom storage
|
119
|
+
def external_copy(target, options={})
|
120
|
+
data = {
|
121
|
+
source: cdn_url(!options.fetch(:strip_operations){ false }),
|
122
|
+
target: target,
|
123
|
+
pattern: options.fetch(:pattern){ nil },
|
124
|
+
make_public: options.fetch(:make_public){ nil },
|
125
|
+
}.reject{|_,v| v.nil?}
|
126
|
+
|
127
|
+
@api.post "/files/", data
|
128
|
+
end
|
129
|
+
|
106
130
|
|
107
131
|
# Datetime methods
|
108
132
|
# practicly try and parse the string to date objects
|
@@ -137,4 +161,4 @@ module Uploadcare
|
|
137
161
|
end
|
138
162
|
end
|
139
163
|
end
|
140
|
-
end
|
164
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Uploadcare
|
2
|
+
module Connections
|
3
|
+
module Auth
|
4
|
+
|
5
|
+
def self.strategy(options)
|
6
|
+
auth_scheme = options.fetch(:auth_scheme)
|
7
|
+
|
8
|
+
unless [:simple, :secure].include?(auth_scheme)
|
9
|
+
raise ArgumentError, "Unknown auth_scheme: '#{auth_scheme}'"
|
10
|
+
end
|
11
|
+
|
12
|
+
klass = const_get(auth_scheme.capitalize)
|
13
|
+
klass.new(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
class Base
|
17
|
+
attr_reader :public_key, :private_key
|
18
|
+
|
19
|
+
def initialize(options)
|
20
|
+
@public_key = options.fetch(:public_key)
|
21
|
+
@private_key = options.fetch(:private_key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def apply(env)
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Uploadcare
|
4
|
+
module Connections
|
5
|
+
module Auth
|
6
|
+
class Secure < Base
|
7
|
+
|
8
|
+
def apply(env)
|
9
|
+
date = Time.now.utc
|
10
|
+
headers(env, date).each{|k, v| env.request_headers[k] = v}
|
11
|
+
env
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def headers(env, date)
|
17
|
+
{
|
18
|
+
"Date" => date.rfc2822,
|
19
|
+
"Authorization" => "Uploadcare #{public_key}:#{signature(env, date)}"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def signature(env, date)
|
24
|
+
sign_string = sign_string(env, date)
|
25
|
+
digest = OpenSSL::Digest.new('sha1')
|
26
|
+
|
27
|
+
OpenSSL::HMAC.hexdigest(digest, private_key, sign_string)
|
28
|
+
end
|
29
|
+
|
30
|
+
def sign_string(env, date)
|
31
|
+
verb = env.method.upcase.to_s
|
32
|
+
uri = env.url.request_uri
|
33
|
+
date_header = date.rfc2822
|
34
|
+
content_type = env.request_headers['Content-Type']
|
35
|
+
content_md5 = OpenSSL::Digest.new('md5').hexdigest(env.body || "")
|
36
|
+
|
37
|
+
[verb, content_md5, content_type, date_header, uri].join("\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Uploadcare
|
2
|
+
module Connections
|
3
|
+
module Auth
|
4
|
+
class Simple < Base
|
5
|
+
|
6
|
+
def apply(env)
|
7
|
+
auth_string = "Uploadcare.Simple #{public_key}:#{private_key}"
|
8
|
+
env.request_headers['Authorization'] = auth_string
|
9
|
+
|
10
|
+
env
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,21 +1,33 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
require "faraday_middleware"
|
3
|
+
|
3
4
|
module Uploadcare
|
4
5
|
module Connections
|
5
6
|
class ApiConnection < Faraday::Connection
|
7
|
+
|
6
8
|
def initialize options
|
7
9
|
super options[:api_url_base] do |frd|
|
8
|
-
|
9
|
-
|
10
|
-
frd.adapter :net_http # actually, default adapter, just to be clear
|
11
|
-
frd.headers['Authorization'] = "Uploadcare.Simple #{options[:public_key]}:#{options[:private_key]}"
|
10
|
+
auth_strategy = Auth.strategy(options)
|
11
|
+
|
12
12
|
frd.headers['Accept'] = "application/vnd.uploadcare-v#{options[:api_version]}+json"
|
13
13
|
frd.headers['User-Agent'] = Uploadcare::user_agent(options)
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
# order of middleware matters!
|
16
|
+
|
17
|
+
# url_encoded changes request body and thus should be before
|
18
|
+
# uploadcare_auth which uses it to sign requests when secure auth
|
19
|
+
# strategy is being used
|
20
|
+
frd.request :url_encoded
|
21
|
+
frd.request :uploadcare_auth, auth_strategy
|
22
|
+
|
23
|
+
frd.response :uploadcare_raise_error
|
24
|
+
frd.response :follow_redirects, limit: 3, callback: lambda{|old, env| auth_strategy.apply(env) }
|
25
|
+
frd.response :uploadcare_parse_json
|
26
|
+
|
27
|
+
frd.adapter :net_http # actually, default adapter, just to be clear
|
17
28
|
end
|
18
29
|
end
|
30
|
+
|
19
31
|
end
|
20
32
|
end
|
21
|
-
end
|
33
|
+
end
|
@@ -12,8 +12,8 @@ module Uploadcare
|
|
12
12
|
frd.adapter :net_http
|
13
13
|
frd.headers['User-Agent'] = Uploadcare::user_agent(options)
|
14
14
|
|
15
|
-
frd.response :
|
16
|
-
frd.response :
|
15
|
+
frd.response :uploadcare_raise_error
|
16
|
+
frd.response :uploadcare_parse_json
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Uploadcare
|
4
|
+
module Connections
|
5
|
+
module Request
|
6
|
+
class Auth < Faraday::Middleware
|
7
|
+
attr_reader :auth_strategy
|
8
|
+
|
9
|
+
def initialize(app=nil, auth_strategy)
|
10
|
+
@auth_strategy = auth_strategy
|
11
|
+
super(app)
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
auth_strategy.apply(env)
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Faraday::Request.register_middleware uploadcare_auth: Uploadcare::Connections::Request::Auth
|
@@ -7,7 +7,7 @@ module Uploadcare
|
|
7
7
|
def on_complete(response)
|
8
8
|
@error_codes = Uploadcare::Error.errors.keys
|
9
9
|
@status = response[:status]
|
10
|
-
|
10
|
+
|
11
11
|
if @error_codes.include?(@status)
|
12
12
|
error = Uploadcare::Error.errors[@status].new
|
13
13
|
fail(error)
|
@@ -18,4 +18,4 @@ module Uploadcare
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
Faraday::Response.register_middleware :
|
21
|
+
Faraday::Response.register_middleware :uploadcare_raise_error => Uploadcare::Connections::Response::RaiseError
|
data/lib/uploadcare/version.rb
CHANGED
data/spec/resources/file_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'uri'
|
3
3
|
require 'socket'
|
4
|
+
require 'securerandom'
|
4
5
|
|
5
6
|
describe Uploadcare::Api::File do
|
6
7
|
before :each do
|
@@ -111,12 +112,111 @@ describe Uploadcare::Api::File do
|
|
111
112
|
end
|
112
113
|
|
113
114
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
115
|
+
describe '#internal_copy' do
|
116
|
+
describe 'integration' do
|
117
|
+
it 'creates an internal copy of the file' do
|
118
|
+
response = retry_if(Uploadcare::Error::RequestError::BadRequest){@file.internal_copy}
|
119
|
+
|
120
|
+
expect( response['type'] ).to eq 'file'
|
121
|
+
expect( response['result']['uuid'] ).not_to eq @file.uuid
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'params' do
|
126
|
+
let(:url_without_ops){ @api.file(SecureRandom.uuid).cdn_url }
|
127
|
+
let(:url_with_ops){ url_without_ops + "-/crop/5x5/center/" }
|
128
|
+
let(:file){ @api.file(url_with_ops) }
|
129
|
+
|
130
|
+
context 'if no params given' do
|
131
|
+
it 'requests server to create an unstored copy with operataions applied' do
|
132
|
+
expect(@api).to receive(:post)
|
133
|
+
.with('/files/', source: url_with_ops)
|
134
|
+
|
135
|
+
file.internal_copy
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'if strip_operations: true given' do
|
140
|
+
it 'passes url without operations as a source for a copy' do
|
141
|
+
expect(@api).to receive(:post)
|
142
|
+
.with('/files/', source: url_without_ops)
|
143
|
+
|
144
|
+
file.internal_copy(strip_operations: true)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'if store: true given' do
|
149
|
+
it 'requests server to create a stored copy' do
|
150
|
+
expect(@api).to receive(:post)
|
151
|
+
.with('/files/', source: url_with_ops, store: true)
|
152
|
+
|
153
|
+
file.internal_copy(store: true)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
120
157
|
end
|
121
158
|
|
122
|
-
|
159
|
+
|
160
|
+
describe '#external_copy' do
|
161
|
+
let(:target){ 'with-prefix' }
|
162
|
+
|
163
|
+
describe 'integration', :payed_feature do
|
164
|
+
it 'creates an external copy of the file' do
|
165
|
+
response = retry_if(Uploadcare::Error::RequestError::BadRequest) do
|
166
|
+
@file.external_copy(target)
|
167
|
+
end
|
168
|
+
|
169
|
+
expect( response['type'] ).to eq 'url'
|
170
|
+
expect( response['result'] ).to match(URI.regexp)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe 'params' do
|
175
|
+
let(:url_without_ops){ @api.file(SecureRandom.uuid).cdn_url }
|
176
|
+
let(:url_with_ops){ url_without_ops + "-/resize/50x50/" }
|
177
|
+
let(:file){ @api.file(url_with_ops) }
|
178
|
+
|
179
|
+
context 'if only target is given' do
|
180
|
+
it 'requests server to create a private copy with default name and with operataions applied' do
|
181
|
+
expect(@api).to receive(:post)
|
182
|
+
.with('/files/', source: url_with_ops, target: target)
|
183
|
+
|
184
|
+
file.external_copy(target)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'if target is not given' do
|
189
|
+
it 'raises ArgumentError' do
|
190
|
+
expect{ file.external_copy }.to raise_error(ArgumentError)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'if strip_operations: true given' do
|
195
|
+
it 'passes url without operations as a source for a copy' do
|
196
|
+
expect(@api).to receive(:post)
|
197
|
+
.with('/files/', source: url_without_ops, target: target)
|
198
|
+
|
199
|
+
file.external_copy(target, strip_operations: true)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'if :make_public given' do
|
204
|
+
it 'requests server to create a copy with correspondent permissions' do
|
205
|
+
expect(@api).to receive(:post)
|
206
|
+
.with('/files/', source: url_with_ops, target: target, make_public: false)
|
207
|
+
|
208
|
+
file.external_copy(target, make_public: false)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'if :pattern given' do
|
213
|
+
it 'requests server to apply given pattern to name of a copy' do
|
214
|
+
expect(@api).to receive(:post)
|
215
|
+
.with('/files/', source: url_with_ops, target: target, pattern: 'test')
|
216
|
+
|
217
|
+
file.external_copy(target, pattern: 'test')
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -3,17 +3,66 @@ require 'uri'
|
|
3
3
|
require 'socket'
|
4
4
|
|
5
5
|
describe Uploadcare::Connections::ApiConnection do
|
6
|
-
|
7
|
-
|
6
|
+
let(:settings){ Uploadcare.default_settings }
|
7
|
+
|
8
|
+
it 'is initializable with default settings' do
|
9
|
+
expect {described_class.new(settings)}.to_not raise_error
|
8
10
|
end
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
|
13
|
+
describe 'default request headers' do
|
14
|
+
subject{ described_class.new(settings).headers }
|
15
|
+
|
16
|
+
it 'includes correct Accept header' do
|
17
|
+
expected = "application/vnd.uploadcare-v#{settings[:api_version]}+json"
|
18
|
+
expect(subject['Accept']).to eq expected
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'includes correct User-Agent header' do
|
22
|
+
expected = Uploadcare::user_agent(settings)
|
23
|
+
expect(subject['User-Agent']).to eq expected
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
describe 'middleware' do
|
29
|
+
subject{ described_class.new(settings).builder.handlers }
|
30
|
+
|
31
|
+
it 'uses Request::Auth middleware' do
|
32
|
+
expect(subject).to include(Uploadcare::Connections::Request::Auth)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'uses Response::ParseJson middleware' do
|
36
|
+
expect(subject).to include(Uploadcare::Connections::Response::ParseJson)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'uses Response::RaiseError middleware' do
|
40
|
+
expect(subject).to include(Uploadcare::Connections::Response::RaiseError)
|
41
|
+
end
|
12
42
|
end
|
13
43
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
44
|
+
|
45
|
+
describe 'auth scheme' do
|
46
|
+
it 'uses simple auth when auth_scheme: :simple setting is provided' do
|
47
|
+
expect(Uploadcare::Connections::Auth::Simple).to receive(:new)
|
48
|
+
described_class.new(settings.merge(auth_scheme: :simple))
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'uses secure auth when auth_scheme: :secure setting is provided' do
|
52
|
+
expect(Uploadcare::Connections::Auth::Secure).to receive(:new)
|
53
|
+
described_class.new(settings.merge(auth_scheme: :secure))
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'raises KeyError when :auth_scheme options is not provided' do
|
57
|
+
expect{
|
58
|
+
described_class.new(settings.reject{|k,_| k == :auth_scheme})
|
59
|
+
}.to raise_error(KeyError)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'raises ArgumentError when provided :auth_scheme is unknown' do
|
63
|
+
expect{
|
64
|
+
described_class.new(settings.merge(auth_scheme: :unknown))
|
65
|
+
}.to raise_error(ArgumentError)
|
66
|
+
end
|
18
67
|
end
|
19
|
-
end
|
68
|
+
end
|