s3direct 0.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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +4 -0
- data/lib/s3direct.rb +42 -0
- data/lib/s3direct/file.rb +72 -0
- data/lib/s3direct/string_interpolator.rb +31 -0
- data/lib/s3direct/upload_request.rb +82 -0
- data/lib/s3direct/uploadable.rb +14 -0
- data/lib/s3direct/version.rb +3 -0
- data/s3direct.gemspec +30 -0
- data/spec/config_spec.rb +16 -0
- data/spec/file_spec.rb +118 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/string_interpolator_spec.rb +20 -0
- data/spec/upload_request_spec.rb +69 -0
- data/spec/uploadable_spec.rb +35 -0
- data/vendor/assets/javascripts/s3direct.js.coffee +99 -0
- data/vendor/assets/javascripts/s3direct_default_template.jst.ejs +14 -0
- metadata +206 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Brent Dillingham
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# S3direct
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 's3direct'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install s3direct
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/s3direct.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require "active_support/all"
|
2
|
+
|
3
|
+
require "s3direct/version"
|
4
|
+
require "s3direct/file"
|
5
|
+
require "s3direct/string_interpolator"
|
6
|
+
require "s3direct/upload_request"
|
7
|
+
require "s3direct/uploadable"
|
8
|
+
|
9
|
+
|
10
|
+
module S3Direct
|
11
|
+
def self.configure
|
12
|
+
yield config
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.config
|
16
|
+
@@config ||= Config.new
|
17
|
+
end
|
18
|
+
|
19
|
+
class Config
|
20
|
+
attr_accessor :bucket
|
21
|
+
attr_accessor :bucket_url
|
22
|
+
attr_accessor :access_key
|
23
|
+
attr_accessor :secret_key
|
24
|
+
|
25
|
+
attr_writer :max_upload_size
|
26
|
+
def max_upload_size
|
27
|
+
@max_upload_size ||= 1.gigabyte
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_writer :default_acl
|
31
|
+
def default_acl
|
32
|
+
@default_acl ||= 'public-read'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
class Engine < Rails::Engine
|
38
|
+
end
|
39
|
+
rescue NameError => e
|
40
|
+
puts "Rails is not loaded => #{e.message}"
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module S3Direct
|
2
|
+
class File
|
3
|
+
|
4
|
+
attr_reader :model, :identifier, :pattern
|
5
|
+
|
6
|
+
def self.sanitize_filename(name)
|
7
|
+
unless name.nil?
|
8
|
+
name.strip
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(model, identifier, pattern, opts={})
|
13
|
+
setup_options opts
|
14
|
+
|
15
|
+
@model = model
|
16
|
+
@identifier = identifier
|
17
|
+
@pattern = pattern
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
@model.send "#{identifier}_file"
|
22
|
+
end
|
23
|
+
|
24
|
+
def s3_path
|
25
|
+
StringInterpolator.new(model, pattern).to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def url
|
29
|
+
if exists?
|
30
|
+
::File.join(config.bucket_url, key)
|
31
|
+
else
|
32
|
+
default_url
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def upload_request(filename = name, options = {})
|
37
|
+
if filename.blank?
|
38
|
+
raise "Can't create an upload request without a filename - " +
|
39
|
+
"provide it as an argument or set #{identifier}_file on the model"
|
40
|
+
end
|
41
|
+
UploadRequest.new s3_path, self.class.sanitize_filename(filename), options
|
42
|
+
end
|
43
|
+
|
44
|
+
def key
|
45
|
+
::File.join(s3_path, name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def exists?
|
49
|
+
name.present?
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def config
|
55
|
+
::S3Direct.config
|
56
|
+
end
|
57
|
+
|
58
|
+
def options
|
59
|
+
# set up any defaults here
|
60
|
+
@options ||= {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_options(opts)
|
64
|
+
options.merge! opts
|
65
|
+
end
|
66
|
+
|
67
|
+
def default_url
|
68
|
+
options[:default_url]
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module S3Direct
|
2
|
+
class StringInterpolator
|
3
|
+
DELIM = "/"
|
4
|
+
|
5
|
+
attr_reader :context, :pattern
|
6
|
+
|
7
|
+
def initialize(context, pattern)
|
8
|
+
@pattern = pattern
|
9
|
+
@context = context
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
compile_parts.join(DELIM)
|
14
|
+
end
|
15
|
+
|
16
|
+
def compile_parts
|
17
|
+
pattern.split(DELIM).collect do |part|
|
18
|
+
if part[0] == ':'
|
19
|
+
meth = part[1, part.length - 1]
|
20
|
+
result = context.public_send meth
|
21
|
+
if result.blank?
|
22
|
+
raise ":#{meth} for path '#{pattern}' was blank in #{context.inspect}"
|
23
|
+
end
|
24
|
+
result.to_s.underscore
|
25
|
+
else
|
26
|
+
part
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module S3Direct
|
2
|
+
class UploadRequest
|
3
|
+
attr_reader :path, :filename, :options
|
4
|
+
|
5
|
+
def initialize(path, sanitized_filename, options = {})
|
6
|
+
@path = path
|
7
|
+
@filename = sanitized_filename
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def key
|
12
|
+
::File.join(@path, @filename)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_json
|
16
|
+
data = {
|
17
|
+
url: config.bucket_url,
|
18
|
+
filename: @filename,
|
19
|
+
key: key,
|
20
|
+
policy: s3_upload_policy_document,
|
21
|
+
signature: s3_upload_signature,
|
22
|
+
acl: s3_acl,
|
23
|
+
success_action_status: "200",
|
24
|
+
'AWSAccessKeyId' => config.access_key
|
25
|
+
}
|
26
|
+
|
27
|
+
if attachment_filename
|
28
|
+
data["Content-Disposition"] = %Q{attachment; filename="#{attachment_filename}"}
|
29
|
+
end
|
30
|
+
|
31
|
+
data.to_json
|
32
|
+
end
|
33
|
+
|
34
|
+
def attachment_filename
|
35
|
+
options[:attachment_filename].presence
|
36
|
+
end
|
37
|
+
|
38
|
+
def s3_acl
|
39
|
+
options.fetch(:acl, config.default_acl)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# generate the policy document that amazon is expecting.
|
45
|
+
def s3_upload_policy_document
|
46
|
+
policy = {
|
47
|
+
'expiration' => 5.minutes.from_now.utc.xmlschema,
|
48
|
+
'conditions' => [
|
49
|
+
{'bucket' => config.bucket},
|
50
|
+
{'acl' => s3_acl},
|
51
|
+
{'success_action_status' => '200'},
|
52
|
+
{'key' => key},
|
53
|
+
['content-length-range', 0, config.max_upload_size]
|
54
|
+
]
|
55
|
+
}
|
56
|
+
|
57
|
+
if attachment_filename
|
58
|
+
policy['conditions'] << {"Content-Disposition" => %Q{attachment; filename="#{attachment_filename}"}}
|
59
|
+
end
|
60
|
+
|
61
|
+
encode(policy.to_json)
|
62
|
+
end
|
63
|
+
|
64
|
+
# sign our request by Base64 encoding the policy document.
|
65
|
+
def s3_upload_signature
|
66
|
+
signature = OpenSSL::HMAC.digest(
|
67
|
+
OpenSSL::Digest::Digest.new('sha1'),
|
68
|
+
config.secret_key,
|
69
|
+
s3_upload_policy_document
|
70
|
+
)
|
71
|
+
encode(signature)
|
72
|
+
end
|
73
|
+
|
74
|
+
def encode(str)
|
75
|
+
Base64.encode64(str).gsub("\n",'')
|
76
|
+
end
|
77
|
+
|
78
|
+
def config
|
79
|
+
::S3Direct.config
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module S3Direct
|
2
|
+
module Uploadable
|
3
|
+
|
4
|
+
def has_s3_file(attr_name, pattern, options={})
|
5
|
+
define_method attr_name do
|
6
|
+
::S3Direct::File.new(self, attr_name, pattern, options)
|
7
|
+
end
|
8
|
+
define_method "#{attr_name}_file=" do |filename|
|
9
|
+
self["#{attr_name}_file"] = ::S3Direct::File.sanitize_filename(filename)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
data/s3direct.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 's3direct/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "s3direct"
|
8
|
+
spec.version = S3Direct::VERSION
|
9
|
+
spec.authors = ["Brent Dillingham"]
|
10
|
+
spec.email = ["brentdillingham@gmail.com"]
|
11
|
+
spec.summary = %q{Upload directly to S3}
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'activesupport', '>= 3.2.0'
|
22
|
+
spec.add_dependency 'jquery-fileupload-rails', '~> 0.4.1'
|
23
|
+
spec.add_dependency 'coffee-rails'
|
24
|
+
spec.add_dependency 'ejs'
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "rails", ">= 3.2.1"
|
28
|
+
spec.add_development_dependency "rake"
|
29
|
+
spec.add_development_dependency "rspec"
|
30
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe S3Direct::Config do
|
4
|
+
it "has a default_acl of public-read when not set" do
|
5
|
+
expect(S3Direct.config.default_acl).to eq('public-read')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "allows setting a default_acl" do
|
9
|
+
begin
|
10
|
+
S3Direct.configure {|c| c.default_acl = 'authenticated-read' }
|
11
|
+
expect(S3Direct.config.default_acl).to eq('authenticated-read')
|
12
|
+
ensure
|
13
|
+
S3Direct.configure {|c| c.default_acl = nil }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/spec/file_spec.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe S3Direct::File do
|
4
|
+
|
5
|
+
it "mounts to an activerecord model on initialize" do
|
6
|
+
model = double(:model)
|
7
|
+
file = S3Direct::File.new(model, :my_file, "foo/bar/bat")
|
8
|
+
|
9
|
+
expect(file.model).to eql model
|
10
|
+
expect(file.identifier).to eql :my_file
|
11
|
+
expect(file.pattern).to eql "foo/bar/bat"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "interpolates an s3 path based on the pattern given" do
|
15
|
+
model = double(:model, lesson_id: 1, id: 2)
|
16
|
+
file = S3Direct::File.new(model, :my_file, "lessons/:lesson_id/videos/:id")
|
17
|
+
|
18
|
+
expect(file.s3_path).to eql "lessons/1/videos/2"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "allows a default url to used when a file is not present" do
|
22
|
+
model = double(:model, lesson_id: 1, id: 2, media_file: nil)
|
23
|
+
file = S3Direct::File.new(model, :media, "lessons/:lesson_id/videos/:id", default_url: "http://example.com/default.png")
|
24
|
+
expect(file.url).to eql "http://example.com/default.png"
|
25
|
+
model.stub(media_file: "foo.png")
|
26
|
+
expect(file.url).to_not eql "http://example.com/default.png"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe S3Direct::File, "#exists?" do
|
32
|
+
|
33
|
+
it "is true if the models '{name}_file' is not nil" do
|
34
|
+
model = double :model, media_file: "foobar.png"
|
35
|
+
file = S3Direct::File.new(model, :media, "foo/bar/bat")
|
36
|
+
expect(file.exists?).to be true
|
37
|
+
end
|
38
|
+
|
39
|
+
it "is false if the models '{name}_file' is nil" do
|
40
|
+
model = double :model, media_file: nil
|
41
|
+
file = S3Direct::File.new(model, :media, "foo/bar/bat")
|
42
|
+
expect(file.exists?).to be false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe S3Direct::File, "#upload_request" do
|
47
|
+
|
48
|
+
it "returns a s3 direct upload object" do
|
49
|
+
model = double(:model, media_file: 'foo')
|
50
|
+
file = S3Direct::File.new(model, :media, "foo/bar/bat")
|
51
|
+
|
52
|
+
expect(file.upload_request).to be_an_instance_of(S3Direct::UploadRequest)
|
53
|
+
expect(file.upload_request.key).to eql(file.key)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "accepts the filename as a parameter and sanitizes it" do
|
57
|
+
model = double(:model)
|
58
|
+
file = S3Direct::File.new(model, :media, "foo/bar/bat")
|
59
|
+
expect(file.upload_request(' foo').filename).to eql 'foo'
|
60
|
+
end
|
61
|
+
|
62
|
+
it "optionally receives options to pass to the UploadRequest" do
|
63
|
+
model = double(:model)
|
64
|
+
file = S3Direct::File.new(model, :media, "foo/bar/bat")
|
65
|
+
S3Direct::UploadRequest.should_receive(:new).with(anything, "test.txt", {foo: 'bar'})
|
66
|
+
file.upload_request('test.txt', {foo: 'bar'})
|
67
|
+
end
|
68
|
+
|
69
|
+
it "raises an error if the filename is not set or provided" do
|
70
|
+
model = double(:model)
|
71
|
+
file = S3Direct::File.new(model, :media, "foo/bar/bat")
|
72
|
+
expect { file.upload_request }.to raise_error
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
describe S3Direct::File, "#name" do
|
78
|
+
it "returns the file name based on the pattern: '<name>_file'" do
|
79
|
+
model = double(:model)
|
80
|
+
model.stub(:avatar_file) { "my_avatar.png" }
|
81
|
+
file = S3Direct::File.new(model, :avatar, "")
|
82
|
+
|
83
|
+
expect(file.name).to eql "my_avatar.png"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe S3Direct::File, "#url" do
|
88
|
+
context "the file exists" do
|
89
|
+
it "returns the full url of the file" do
|
90
|
+
S3Direct.config.stub(bucket_url: 'http://s3.com/mabucket/')
|
91
|
+
model = double(:model, id: 77, avatar_file: 'my_avatar.png')
|
92
|
+
file = S3Direct::File.new(model, :avatar, "my_model/:id/avatar")
|
93
|
+
|
94
|
+
expect(file.url).to eql "http://s3.com/mabucket/my_model/77/avatar/my_avatar.png"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "the file does not exist" do
|
99
|
+
it "returns nil" do
|
100
|
+
model = double(:model)
|
101
|
+
model.stub(:avatar_file) { nil }
|
102
|
+
file = S3Direct::File.new(model, :avatar, "foo/bar/bat")
|
103
|
+
|
104
|
+
expect(file.url).to be_nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe S3Direct::File, "#key" do
|
110
|
+
it "returns the full s3 key for the file" do
|
111
|
+
model = double(:model)
|
112
|
+
model.stub(:id) { 77 }
|
113
|
+
model.stub(:avatar_file) { "my_avatar.png" }
|
114
|
+
file = S3Direct::File.new(model, :avatar, "my_model/:id/avatar/")
|
115
|
+
|
116
|
+
expect(file.key).to eql "my_model/77/avatar/my_avatar.png"
|
117
|
+
end
|
118
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "ostruct"
|
3
|
+
|
4
|
+
describe S3Direct::StringInterpolator do
|
5
|
+
|
6
|
+
it "converts :pattern tags using local context methods" do
|
7
|
+
foo = OpenStruct.new(bar: "bar", fizz: "fizzy", buzz: "buzz_buzz")
|
8
|
+
interpolation = S3Direct::StringInterpolator.new(foo, "foo/bar/:bar/fizz/:fizz/buzz/:buzz/:class")
|
9
|
+
expect(interpolation.to_s).to eq "foo/bar/bar/fizz/fizzy/buzz/buzz_buzz/open_struct"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "will not re-interpolate :pattern returned from method calls" do
|
13
|
+
o = OpenStruct.new
|
14
|
+
o.security = ":test"
|
15
|
+
o.test = "boom"
|
16
|
+
interpolation = S3Direct::StringInterpolator.new(o, "/security/:security")
|
17
|
+
expect(interpolation.to_s).to eq '/security/:test'
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe S3Direct::UploadRequest, '#attachment_filename' do
|
4
|
+
it 'is nil if not provided an attachment_filename option' do
|
5
|
+
upload_request = S3Direct::UploadRequest.new('/foo/bar', 'buzz.txt')
|
6
|
+
expect(upload_request.attachment_filename).to be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'is the options[:attachment_filename] if provided' do
|
10
|
+
upload_request = S3Direct::UploadRequest.new('/foo/bar', 'random.txt', {
|
11
|
+
attachment_filename: 'expected.txt'
|
12
|
+
})
|
13
|
+
expect(upload_request.attachment_filename).to eq('expected.txt')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe S3Direct::UploadRequest, '#to_json' do
|
18
|
+
before do
|
19
|
+
S3Direct.config.stub(bucket_url: 'http://s3.com/mabucket/')
|
20
|
+
S3Direct.config.stub(secret_key: 'sekret')
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when no attachment_filename option is given" do
|
24
|
+
before do
|
25
|
+
upload_request = S3Direct::UploadRequest.new('/foo/bar', 'buzz.txt')
|
26
|
+
@data = JSON[upload_request.to_json]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "does not add a Content-Disposition attachment field" do
|
30
|
+
expect(@data.has_key?("Content-Disposition")).to be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
it "includes no content-disposition in the policy" do
|
34
|
+
policy = JSON[Base64.decode64 @data['policy']]
|
35
|
+
expect(policy['conditions'].include?('Content-Disposition')).to be_false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when an attachment_filename option is given" do
|
40
|
+
before do
|
41
|
+
upload_request = S3Direct::UploadRequest.new('/foo/bar', 'buzz.txt', {
|
42
|
+
attachment_filename: 'expected.txt'
|
43
|
+
})
|
44
|
+
@data = JSON[upload_request.to_json]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "adds a Content-Disposition attachment field using the option" do
|
48
|
+
expect(@data["Content-Disposition"]).to eq('attachment; filename="expected.txt"')
|
49
|
+
end
|
50
|
+
|
51
|
+
it "includes the content-disposition in the policy" do
|
52
|
+
policy = JSON[Base64.decode64 @data['policy']]
|
53
|
+
condition = policy['conditions'].detect {|c| c.is_a?(Hash) && c.keys.include?('Content-Disposition') }
|
54
|
+
expect(condition['Content-Disposition']).to eq('attachment; filename="expected.txt"')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe S3Direct::UploadRequest, '#s3_acl' do
|
60
|
+
it "uses the config default_acl by default" do
|
61
|
+
upload_request = S3Direct::UploadRequest.new('/foo/bar', 'buzz.txt')
|
62
|
+
expect(upload_request.s3_acl).to eq('public-read')
|
63
|
+
end
|
64
|
+
|
65
|
+
it "uses the acl option if available" do
|
66
|
+
upload_request = S3Direct::UploadRequest.new('/foo/bar', 'buzz.txt', acl: 'authenticated-read')
|
67
|
+
expect(upload_request.s3_acl).to eq('authenticated-read')
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module TestS3Uploadable
|
4
|
+
class Base
|
5
|
+
end
|
6
|
+
|
7
|
+
Base.extend S3Direct::Uploadable
|
8
|
+
|
9
|
+
class Foo < Base
|
10
|
+
has_s3_file :avatar, "foo/:id/avatar", default_url: "foobar.png"
|
11
|
+
|
12
|
+
def avatar_file
|
13
|
+
"avatar.png"
|
14
|
+
end
|
15
|
+
|
16
|
+
def id
|
17
|
+
42
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe S3Direct::Uploadable, "#has_file" do
|
24
|
+
it "defines a method on the object to access the file" do
|
25
|
+
foo = TestS3Uploadable::Foo.new
|
26
|
+
expect(foo.avatar).to be_kind_of(S3Direct::File)
|
27
|
+
expect(foo.avatar.s3_path).to eql "foo/42/avatar"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "allows for default urls" do
|
31
|
+
foo = TestS3Uploadable::Foo.new
|
32
|
+
foo.stub(:avatar_file) { nil }
|
33
|
+
expect(foo.avatar.url).to eql "foobar.png"
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#= require jquery-fileupload/basic
|
2
|
+
#= require s3direct_default_template
|
3
|
+
#= require_self
|
4
|
+
|
5
|
+
class window.S3DirectUploadView
|
6
|
+
template: JST['s3_direct_default_template']
|
7
|
+
|
8
|
+
$: (selector) ->
|
9
|
+
@$el.find(selector)
|
10
|
+
|
11
|
+
uploader: ->
|
12
|
+
@$('.uploader')
|
13
|
+
|
14
|
+
fileInput: ->
|
15
|
+
@$('input[type=file]')
|
16
|
+
|
17
|
+
progressMeter: ->
|
18
|
+
@$('.progress .meter')
|
19
|
+
|
20
|
+
progressBar: ->
|
21
|
+
@$('.progress')
|
22
|
+
|
23
|
+
uploadInfo: ->
|
24
|
+
@$('.upload-info')
|
25
|
+
|
26
|
+
alertBox: ->
|
27
|
+
@$('.alert-box')
|
28
|
+
|
29
|
+
fileInfo: ->
|
30
|
+
@$('.file-info')
|
31
|
+
|
32
|
+
constructor: (options = {}) ->
|
33
|
+
@model = options.model
|
34
|
+
@fileAttribute = options.fileAttribute
|
35
|
+
@requestUrl = options.requestUrl
|
36
|
+
@$el = if options.el then $(options.el) else $('<div>')
|
37
|
+
@el = @$el[0]
|
38
|
+
|
39
|
+
render: ->
|
40
|
+
@$el.html @template(this)
|
41
|
+
@setupFileInput()
|
42
|
+
@setFileInfo @fileName()
|
43
|
+
|
44
|
+
fileName: ->
|
45
|
+
@model.get(@fileAttribute)
|
46
|
+
|
47
|
+
setupFileInput: ->
|
48
|
+
filename = null
|
49
|
+
|
50
|
+
@fileInput()
|
51
|
+
.fileupload
|
52
|
+
paramName : 'file'
|
53
|
+
autoUpload : true
|
54
|
+
dropZone : @$el
|
55
|
+
# When a file is added, make an "upload request" to our server to get the S3 bucket URL,
|
56
|
+
# policy document, signature, etc.
|
57
|
+
add: (e, data) =>
|
58
|
+
$.ajax
|
59
|
+
url: @requestUrl
|
60
|
+
data: {filename: data.files[0].name}
|
61
|
+
success: (uploadRequestObj) =>
|
62
|
+
# The sanitized version of the filename
|
63
|
+
filename = uploadRequestObj.filename
|
64
|
+
@fileInput().fileupload 'option', 'url', uploadRequestObj.url
|
65
|
+
@fileInput().fileupload 'option', 'formData', _.omit(uploadRequestObj, 'url', 'filename')
|
66
|
+
data.submit()
|
67
|
+
# Once the upload to S3 is under way, fade in the progress bar and disable the button.
|
68
|
+
send: =>
|
69
|
+
@uploader().hide()
|
70
|
+
@alertBox().remove()
|
71
|
+
@progressBar().fadeIn()
|
72
|
+
# Show an indeterminate progress bar if browser doesn't support progress (I *think* this is the right attribute to check)
|
73
|
+
@progressMeter().css(width: '100%') unless $.support.xhrFileUpload
|
74
|
+
# Update the progress bar.
|
75
|
+
progress: (e) =>
|
76
|
+
percent = Math.round(e.loaded / e.total * 100)
|
77
|
+
@progressMeter().css(width: percent + '%')
|
78
|
+
# S3 upload was successful; save the LessonMedia object on the server.
|
79
|
+
done: (e, response) =>
|
80
|
+
msg = "<b>Upload succeeded.</b>"
|
81
|
+
@showUploadMessage(msg, true)
|
82
|
+
@model.set @fileAttribute, filename
|
83
|
+
@model.save()
|
84
|
+
# S3 upload failed.
|
85
|
+
error: =>
|
86
|
+
msg = "<b>Something went wrong!</b> Your upload did not complete. Please try again."
|
87
|
+
@showUploadMessage(msg, false)
|
88
|
+
@uploader().show()
|
89
|
+
|
90
|
+
# Displays a message about the success or failure of the upload.
|
91
|
+
showUploadMessage: (msg, wasSuccess) ->
|
92
|
+
@progressBar().fadeOut 300, =>
|
93
|
+
@progressMeter().css(width: 0)
|
94
|
+
@alertBox().remove()
|
95
|
+
alertClass = if wasSuccess then 'success' else 'alert'
|
96
|
+
@uploadInfo().append("<div class='alert-box #{alertClass}'>#{msg}</div>")
|
97
|
+
|
98
|
+
setFileInfo: (name) ->
|
99
|
+
@fileInfo().text name if name
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<div class="s3-direct">
|
2
|
+
<div class="uploader">
|
3
|
+
<span class="button secondary small fileinput-button">
|
4
|
+
<span>Choose File</span>
|
5
|
+
<input type="file" />
|
6
|
+
</span>
|
7
|
+
<span class="file-info">No file chosen</span>
|
8
|
+
</div>
|
9
|
+
<div class="upload-info">
|
10
|
+
<div class="progress" style="display:none">
|
11
|
+
<span class="meter" style="width:0%"></span>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
</div>
|
metadata
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: s3direct
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brent Dillingham
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: jquery-fileupload-rails
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.4.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.4.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: coffee-rails
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: ejs
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: bundler
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.3'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.3'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rails
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 3.2.1
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 3.2.1
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rake
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rspec
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
description: Upload directly to S3
|
143
|
+
email:
|
144
|
+
- brentdillingham@gmail.com
|
145
|
+
executables: []
|
146
|
+
extensions: []
|
147
|
+
extra_rdoc_files: []
|
148
|
+
files:
|
149
|
+
- .gitignore
|
150
|
+
- Gemfile
|
151
|
+
- LICENSE.txt
|
152
|
+
- README.md
|
153
|
+
- Rakefile
|
154
|
+
- lib/s3direct.rb
|
155
|
+
- lib/s3direct/file.rb
|
156
|
+
- lib/s3direct/string_interpolator.rb
|
157
|
+
- lib/s3direct/upload_request.rb
|
158
|
+
- lib/s3direct/uploadable.rb
|
159
|
+
- lib/s3direct/version.rb
|
160
|
+
- s3direct.gemspec
|
161
|
+
- spec/config_spec.rb
|
162
|
+
- spec/file_spec.rb
|
163
|
+
- spec/spec_helper.rb
|
164
|
+
- spec/string_interpolator_spec.rb
|
165
|
+
- spec/upload_request_spec.rb
|
166
|
+
- spec/uploadable_spec.rb
|
167
|
+
- vendor/assets/javascripts/s3direct.js.coffee
|
168
|
+
- vendor/assets/javascripts/s3direct_default_template.jst.ejs
|
169
|
+
homepage: ''
|
170
|
+
licenses:
|
171
|
+
- MIT
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
segments:
|
183
|
+
- 0
|
184
|
+
hash: 1023143937309546202
|
185
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
|
+
none: false
|
187
|
+
requirements:
|
188
|
+
- - ! '>='
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '0'
|
191
|
+
segments:
|
192
|
+
- 0
|
193
|
+
hash: 1023143937309546202
|
194
|
+
requirements: []
|
195
|
+
rubyforge_project:
|
196
|
+
rubygems_version: 1.8.23
|
197
|
+
signing_key:
|
198
|
+
specification_version: 3
|
199
|
+
summary: Upload directly to S3
|
200
|
+
test_files:
|
201
|
+
- spec/config_spec.rb
|
202
|
+
- spec/file_spec.rb
|
203
|
+
- spec/spec_helper.rb
|
204
|
+
- spec/string_interpolator_spec.rb
|
205
|
+
- spec/upload_request_spec.rb
|
206
|
+
- spec/uploadable_spec.rb
|