mongoid-direct-s3-upload 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +250 -0
- data/Rakefile +31 -0
- data/app/assets/javascripts/s3_relay.coffee +118 -0
- data/app/assets/stylesheets/s3_relay.css +31 -0
- data/app/controllers/s3_relay/uploads_controller.rb +71 -0
- data/app/helpers/s3_relay/uploads_helper.rb +24 -0
- data/app/models/s3_relay/upload.rb +73 -0
- data/config/routes.rb +5 -0
- data/lib/s3_relay.rb +4 -0
- data/lib/s3_relay/base.rb +35 -0
- data/lib/s3_relay/engine.rb +16 -0
- data/lib/s3_relay/model.rb +51 -0
- data/lib/s3_relay/private_url.rb +37 -0
- data/lib/s3_relay/s3_relay.rb +4 -0
- data/lib/s3_relay/upload_presigner.rb +61 -0
- data/lib/s3_relay/version.rb +3 -0
- data/test/controllers/s3_relay/uploads_controller_test.rb +144 -0
- data/test/dummy/README.md +24 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/config/manifest.js +3 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/javascripts/cable.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/channels/application_cable/channel.rb +4 -0
- data/test/dummy/app/channels/application_cable/connection.rb +4 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/jobs/application_job.rb +2 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/models/application_record.rb +3 -0
- data/test/dummy/app/models/product.rb +6 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +9 -0
- data/test/dummy/bin/rake +9 -0
- data/test/dummy/bin/setup +38 -0
- data/test/dummy/bin/spring +17 -0
- data/test/dummy/bin/update +29 -0
- data/test/dummy/bin/yarn +11 -0
- data/test/dummy/config.ru +5 -0
- data/test/dummy/config/application.rb +19 -0
- data/test/dummy/config/boot.rb +3 -0
- data/test/dummy/config/cable.yml +10 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +54 -0
- data/test/dummy/config/environments/production.rb +91 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/test/dummy/config/initializers/assets.rb +14 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +33 -0
- data/test/dummy/config/puma.rb +56 -0
- data/test/dummy/config/routes.rb +7 -0
- data/test/dummy/config/secrets.yml +32 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/db/migrate/20141021002149_create_products.rb +9 -0
- data/test/dummy/db/schema.rb +41 -0
- data/test/dummy/db/seeds.rb +7 -0
- data/test/dummy/package.json +5 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/test/dummy/public/apple-touch-icon.png +0 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/robots.txt +1 -0
- data/test/dummy/test/application_system_test_case.rb +5 -0
- data/test/dummy/test/test_helper.rb +9 -0
- data/test/factories/products.rb +5 -0
- data/test/factories/uploads.rb +23 -0
- data/test/helpers/s3_relay/uploads_helper_test.rb +29 -0
- data/test/lib/s3_relay/model_test.rb +66 -0
- data/test/lib/s3_relay/private_url_test.rb +28 -0
- data/test/lib/s3_relay/upload_presigner_test.rb +38 -0
- data/test/models/s3_relay/upload_test.rb +142 -0
- data/test/support/database_cleaner.rb +14 -0
- data/test/test_helper.rb +29 -0
- metadata +356 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2566dba40ae75b7fc32541abd4a5732ae16c4fed
|
4
|
+
data.tar.gz: 3ff5182fbec54c955c3d0f5e7380cb0932711027
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d1f57ed9c72a90eda341ffb8815bab7556631839494789e07a374b704a44bcfe584292ed49f6f23dc9b35a1775b05e946bf12209f11ad9429fb895757eec18df
|
7
|
+
data.tar.gz: dbc65a1713bb8c91180676bbaec241269dcfb202ba9c5b8774e03527809722fe825b511f554514a53a4cc734c83acf27882c89e3b5428e0bf735f0f982b56f75
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 Kenny Johnston
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
# Mongoid Direct Upload to Amazon S3
|
2
|
+
|
3
|
+
Or `s3_relay_mongoid`
|
4
|
+
|
5
|
+
[Original s3_relay Gem for ActiveRecord](http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html)
|
6
|
+
|
7
|
+
Enables direct file uploads to Amazon S3 and provides a flexible pattern for
|
8
|
+
your Rails app to asynchronously ingest the files.
|
9
|
+
|
10
|
+
## Overview
|
11
|
+
|
12
|
+
This Rails engine allows you to quickly implement direct uploads to Amazon S3
|
13
|
+
from your Rails 3.1+ / 4.x / 5.x application. It does not depend on any specific file
|
14
|
+
upload libraries, UI frameworks or AWS gems, like other solutions tend to.
|
15
|
+
|
16
|
+
It works by utilizing Amazon S3's
|
17
|
+
[Cross-Origin Resource Sharing](http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html)
|
18
|
+
to permit browser-based uploads directly to S3 with presigned URLs generated by
|
19
|
+
this gem with your application's API credentials. As each file is uploaded,
|
20
|
+
the gem persists detail about the uploaded file in your application's database.
|
21
|
+
This table should be thought of much like a queue - think
|
22
|
+
[DelayedJob](https://github.com/collectiveidea/delayed_job) for your
|
23
|
+
uploaded-but-not-yet-ingested file uploads.
|
24
|
+
|
25
|
+
How (and if) you choose to import each uploaded file into your processing
|
26
|
+
library of choice is completely up to you. The gem tracks the state of each
|
27
|
+
upload so that you may used the provided `.pending` scope and `mark_imported!`
|
28
|
+
method to fetch, process (via your background processor of choice), then
|
29
|
+
mark-off each upload record whose file has been successfully ingested by your
|
30
|
+
app.
|
31
|
+
|
32
|
+
## Features
|
33
|
+
|
34
|
+
* Files can be uploaded before or after your parent object has been saved.
|
35
|
+
* File upload fields can be placed inside or outside of your parent object's
|
36
|
+
form.
|
37
|
+
* File uploads display completion progress.
|
38
|
+
* Boilerplate styling can be used or easily replaced.
|
39
|
+
* All uploads are marked for [Server-Side Encryption by AWS](http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingEncryption.html) so that they are encrypted upon
|
40
|
+
write to disk. S3 then decrypts and streams the files as they are downloaded.
|
41
|
+
* Models can have multiple types of uploads and specify for each if only one
|
42
|
+
file is permitted or if multiple are.
|
43
|
+
* Multiple files can upload concurrently to S3.
|
44
|
+
|
45
|
+
## Technology & Requirements
|
46
|
+
|
47
|
+
Uploads are made possible by use of the `FormData` object, defined in
|
48
|
+
[XMLHttpRequest Level 2](http://dev.w3.org/2006/webapi/XMLHttpRequest-2/).
|
49
|
+
Many people are broadly referring to this as being provided by HTML5, but
|
50
|
+
technically it's part of the aforementioned spec that browsers have been
|
51
|
+
adhering to for a couple of major versions now. Even IE, wuh?
|
52
|
+
|
53
|
+
The latest versions of all of the following are ideal, but here are the gem's
|
54
|
+
minimum requirements:
|
55
|
+
|
56
|
+
* Modern versions of Chrome, Safari, FireFox or IE 10+
|
57
|
+
* Note: Progress bars are currently disabled in IE
|
58
|
+
* Note: IE <= 9 users will be instructed to upgrade their browser upon
|
59
|
+
selecting a file
|
60
|
+
|
61
|
+
## Demo
|
62
|
+
|
63
|
+
See the ActiveRecord demo application using `s3_relay` [here](https://github.com/kjohnston/s3_relay-demo).
|
64
|
+
|
65
|
+
## Configuring CORS
|
66
|
+
|
67
|
+
Edit your S3 bucket's CORS Configuration to resemble the following:
|
68
|
+
|
69
|
+
```
|
70
|
+
<CORSConfiguration>
|
71
|
+
<CORSRule>
|
72
|
+
<AllowedOrigin>*</AllowedOrigin>
|
73
|
+
<AllowedMethod>POST</AllowedMethod>
|
74
|
+
<AllowedHeader>Content-Type</AllowedHeader>
|
75
|
+
<AllowedHeader>origin</AllowedHeader>
|
76
|
+
</CORSRule>
|
77
|
+
</CORSConfiguration>
|
78
|
+
```
|
79
|
+
|
80
|
+
Note: The example above is a starting point for development. Obviously, you
|
81
|
+
don't want to permit requests from any domain to upload to your S3 bucket.
|
82
|
+
Please see the [AWS Documentation](http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html)
|
83
|
+
to learn how to lock it down further.
|
84
|
+
|
85
|
+
## Installation
|
86
|
+
|
87
|
+
* Add `gem "mongoid-direct-s3-upload"` to your Gemfile and run `bundle`.
|
88
|
+
* Add migrations to your app with `rake s3_relay:install:migrations db:migrate`.
|
89
|
+
* Add `mount S3Relay::Engine => "/s3_relay"` to the top of your routes file.
|
90
|
+
* Add `require s3_relay` to your JavaScript manifest.
|
91
|
+
* [Optional] Add `require s3_relay` to your Style Sheet manifest.
|
92
|
+
* Add the following environment variables to your app:
|
93
|
+
|
94
|
+
`./config/initializers/mongoid-direct-s3-upload.rb`
|
95
|
+
|
96
|
+
```
|
97
|
+
ENV["S3_RELAY_ACCESS_KEY_ID"]="abc123"
|
98
|
+
ENV["S3_RELAY_SECRET_ACCESS_KEY"]="xzy456"
|
99
|
+
ENV["S3_RELAY_REGION"]="us-west-2"
|
100
|
+
ENV["S3_RELAY_BUCKET"]="some-s3-bucket"
|
101
|
+
ENV["S3_RELAY_ACL"]="public-read"
|
102
|
+
```
|
103
|
+
|
104
|
+
## Use
|
105
|
+
|
106
|
+
### Add upload definitions to your model
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class Product
|
110
|
+
include Mongoid::Document
|
111
|
+
extend S3Relay::Model
|
112
|
+
s3_relay :icon
|
113
|
+
s3_relay :photo_uploads, has_many: true
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
### Restricting uploads to authenticated users
|
118
|
+
|
119
|
+
If your app's file uploads need to be restricted to logged in users, simply
|
120
|
+
override the following method in your application controller to call any
|
121
|
+
authentication method you're currently using.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
def authenticate_for_s3_relay
|
125
|
+
authenticate_user! # Devise example
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
### Add virtual attributes to your controller's Strong Parameters config
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
product_params = params.require(:product)
|
133
|
+
.permit(:name, :new_icon_uuids: [], new_photo_uploads_uuids: [])
|
134
|
+
|
135
|
+
@product = Product.new(product_params)
|
136
|
+
```
|
137
|
+
|
138
|
+
### Add file upload fields to your views
|
139
|
+
|
140
|
+
```erb
|
141
|
+
<%= s3_relay_field @product, :icon %>
|
142
|
+
<%= s3_relay_field @product, :photo_uploads, multiple: true %>
|
143
|
+
```
|
144
|
+
|
145
|
+
* By default the content-disposition on the files stored in the uploads bucket
|
146
|
+
will be set to inline. You can optionally set it to attachment by passing that
|
147
|
+
option like so:
|
148
|
+
|
149
|
+
```erb
|
150
|
+
<%= s3_relay_field @artist, :mp3_uploads, multiple: true, disposition: "attachment" %>
|
151
|
+
```
|
152
|
+
|
153
|
+
### View file on Amazon S3
|
154
|
+
|
155
|
+
```erb
|
156
|
+
<%= image_tag @product.icon.public_url %>
|
157
|
+
```
|
158
|
+
|
159
|
+
### Importing files back to the server for processing
|
160
|
+
|
161
|
+
#### Processing uploads asynchronously
|
162
|
+
|
163
|
+
Use your background job processor of choice to process uploads pending
|
164
|
+
ingestion (and image processing) by your app.
|
165
|
+
|
166
|
+
Say you're using [Resque](https://github.com/resque/resque) and [CarrierWave](https://github.com/carrierwaveuploader/carrierwave), you could define a job class:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class ProductPhoto::Import
|
170
|
+
@queue = :photo_import
|
171
|
+
|
172
|
+
def self.perform(product_id, upload_id)
|
173
|
+
@product = Product.find(product_id)
|
174
|
+
@upload = S3Relay::Upload.find(upload_id)
|
175
|
+
|
176
|
+
@product.photos.create!(remote_file_url: @upload.private_url)
|
177
|
+
@upload.mark_imported!
|
178
|
+
end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
#### Triggering upload imports for existing parent objects
|
183
|
+
|
184
|
+
If you would like to immediately enqueue a job to begin importing an upload
|
185
|
+
into its final desination, simply define a method on your parent object
|
186
|
+
called `import_upload` and that method will be called after an `S3Relay::Upload`
|
187
|
+
is created.
|
188
|
+
|
189
|
+
#### Triggering upload imports for new parent objects
|
190
|
+
|
191
|
+
If you would like to immediately enqueue a job to begin importing all of the
|
192
|
+
uploads for a new parent object following its creation, you might want to setup
|
193
|
+
a callback to enqueue those imports.
|
194
|
+
|
195
|
+
#### Examples
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
class Product
|
199
|
+
|
200
|
+
# Called by s3_relay when an associated S3Relay::Upload object is created
|
201
|
+
def import_upload(upload_id)
|
202
|
+
Resque.enqueue(ProductPhoto::Import, id, upload_id)
|
203
|
+
end
|
204
|
+
|
205
|
+
after_commit :import_uploads, on: :create
|
206
|
+
|
207
|
+
# Called via after_commit to enqueue imports of S3Relay::Upload objects
|
208
|
+
def import_uploads
|
209
|
+
photo_uploads.pending.each do |upload|
|
210
|
+
Resque.enqueue(ProductPhoto::Import, id, upload.id)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
### Restricting the objects uploads can be associated with
|
218
|
+
|
219
|
+
Remember the time when that guy found a way to a submit Github form in such a
|
220
|
+
way that it linked a new SSH key he provided to DHH's user record? No bueno.
|
221
|
+
Don't let your users attach files to objects they don't have access to.
|
222
|
+
|
223
|
+
You can prevent this by defining a method in ApplicationController that
|
224
|
+
filters out the parent object params passed during upload creation if your logic
|
225
|
+
finds that the user doesn't have access to the parent object in question. Ex:
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
def order_file_uploads_params(parent)
|
229
|
+
if parent.user == current_user
|
230
|
+
# Yep, that's your order, you can add files to it
|
231
|
+
{ parent: parent }
|
232
|
+
else
|
233
|
+
# Nope, you're trying to add a file to someone else's order, or whatever
|
234
|
+
{ }
|
235
|
+
end
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
## Contributing
|
240
|
+
|
241
|
+
1. [Fork it](https://github.com/kjohnston/s3_relay/fork)
|
242
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
243
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
244
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
245
|
+
5. [Create a Pull Request](https://github.com/kjohnston/s3_relay/pull/new)
|
246
|
+
|
247
|
+
## License
|
248
|
+
|
249
|
+
* Freely distributable and licensed under the [MIT license](http://kjohnston.mit-license.org/license.html).
|
250
|
+
* Copyright (c) 2014 Kenny Johnston
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
begin
|
2
|
+
require "bundler/setup"
|
3
|
+
rescue LoadError
|
4
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
5
|
+
end
|
6
|
+
|
7
|
+
require "rdoc/task"
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = "rdoc"
|
11
|
+
rdoc.title = "S3Relay"
|
12
|
+
rdoc.options << "--line-numbers"
|
13
|
+
rdoc.rdoc_files.include("README.rdoc")
|
14
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load "rails/tasks/engine.rake"
|
19
|
+
|
20
|
+
Bundler::GemHelper.install_tasks
|
21
|
+
|
22
|
+
require "rake/testtask"
|
23
|
+
|
24
|
+
Rake::TestTask.new(:test) do |t|
|
25
|
+
t.libs << "lib"
|
26
|
+
t.libs << "test"
|
27
|
+
t.pattern = "test/**/*_test.rb"
|
28
|
+
t.verbose = false
|
29
|
+
end
|
30
|
+
|
31
|
+
task default: :test
|
@@ -0,0 +1,118 @@
|
|
1
|
+
displayFailedUpload = (progressColumn=null) ->
|
2
|
+
if progressColumn
|
3
|
+
progressColumn.text("File could not be uploaded")
|
4
|
+
else
|
5
|
+
alert("File could not be uploaded")
|
6
|
+
|
7
|
+
publishEvent = (target, name, detail) ->
|
8
|
+
$(target).trigger( name, detail )
|
9
|
+
|
10
|
+
saveUrl = (container, uuid, filename, contentType, publicUrl, progressColumn, fileColumn) ->
|
11
|
+
privateUrl = null
|
12
|
+
|
13
|
+
$.ajax
|
14
|
+
type: "POST"
|
15
|
+
url: "/s3_relay/uploads"
|
16
|
+
data:
|
17
|
+
parent_type: container.data("parentType")
|
18
|
+
parent_id: container.data("parentId")
|
19
|
+
association: container.data("association")
|
20
|
+
uuid: uuid
|
21
|
+
filename: filename
|
22
|
+
content_type: contentType
|
23
|
+
public_url: publicUrl
|
24
|
+
success: (data, status, xhr) ->
|
25
|
+
privateUrl = data.private_url
|
26
|
+
if privateUrl == null
|
27
|
+
displayFailedUpload(progressColumn)
|
28
|
+
else
|
29
|
+
fileColumn.html("<a href='#{privateUrl}' target='_blank'>#{filename}</a>")
|
30
|
+
|
31
|
+
virtualAttr = "#{container.data('parentType')}[new_#{container.data('association')}_uuids]"
|
32
|
+
hiddenField = "<input type='hidden' name='#{virtualAttr}[]' value='#{uuid}' />"
|
33
|
+
container.append(hiddenField)
|
34
|
+
publishEvent(container, "upload:success", [ uuid, filename, privateUrl ])
|
35
|
+
error: (xhr) ->
|
36
|
+
console.log xhr.responseText
|
37
|
+
|
38
|
+
return privateUrl
|
39
|
+
|
40
|
+
uploadFiles = (container) ->
|
41
|
+
fileInput = $("input.s3r-field", container)
|
42
|
+
files = fileInput.get(0).files
|
43
|
+
uploadFile(container, file) for file in files
|
44
|
+
fileInput.val("")
|
45
|
+
|
46
|
+
uploadFile = (container, file) ->
|
47
|
+
fileName = file.name
|
48
|
+
|
49
|
+
# Assign unique value to each request so Safari doesn't consolidate them
|
50
|
+
@s3r_upload_index ||= 0
|
51
|
+
@s3r_upload_index += 1
|
52
|
+
|
53
|
+
$.ajax
|
54
|
+
type: "GET"
|
55
|
+
url: "/s3_relay/uploads/new?s3r_upload_index=#{s3r_upload_index}"
|
56
|
+
success: (data, status, xhr) ->
|
57
|
+
formData = new FormData()
|
58
|
+
xhr = new XMLHttpRequest()
|
59
|
+
endpoint = data.endpoint
|
60
|
+
disposition = container.data("disposition")
|
61
|
+
|
62
|
+
formData.append("AWSAccessKeyID", data.awsaccesskeyid)
|
63
|
+
formData.append("x-amz-server-side-encryption", data.x_amz_server_side_encryption)
|
64
|
+
formData.append("key", data.key)
|
65
|
+
formData.append("success_action_status", data.success_action_status)
|
66
|
+
formData.append("acl", data.acl)
|
67
|
+
formData.append("policy", data.policy)
|
68
|
+
formData.append("signature", data.signature)
|
69
|
+
formData.append("content-type", file.type)
|
70
|
+
formData.append("content-disposition", "#{disposition}; filename=\"#{fileName}\"")
|
71
|
+
formData.append("file", file)
|
72
|
+
|
73
|
+
uuid = data.uuid
|
74
|
+
|
75
|
+
uploadList = $(".s3r-upload-list", container)
|
76
|
+
uploadList.prepend("<tr id='#{uuid}'><td><div class='s3r-file-url'>#{fileName}</div></td><td class='s3r-progress'><div class='s3r-bar' style='display: none;'><div class='s3r-meter'></div></div></td></tr>")
|
77
|
+
fileColumn = $(".s3r-upload-list ##{uuid} .s3r-file-url", container)
|
78
|
+
progressColumn = $(".s3r-upload-list ##{uuid} .s3r-progress", container)
|
79
|
+
progressBar = $(".s3r-bar", progressColumn)
|
80
|
+
progressMeter = $(".s3r-meter", progressColumn)
|
81
|
+
|
82
|
+
xhr.upload.addEventListener "progress", (ev) ->
|
83
|
+
if ev.loaded
|
84
|
+
percentage = ((ev.loaded / ev.total) * 100.0).toFixed(0)
|
85
|
+
progressBar.show()
|
86
|
+
progressMeter.css "width", "#{percentage}%"
|
87
|
+
else
|
88
|
+
progressColumn.text("Uploading...") # IE can't get position
|
89
|
+
|
90
|
+
xhr.onreadystatechange = (ev) ->
|
91
|
+
if xhr.readyState is 4
|
92
|
+
progressColumn.text("") # IE can't get position
|
93
|
+
progressBar.remove()
|
94
|
+
|
95
|
+
if xhr.status == 201
|
96
|
+
contentType = file.type
|
97
|
+
publicUrl = $("Location", xhr.responseXML).text()
|
98
|
+
saveUrl(container, uuid, fileName, contentType, publicUrl, progressColumn, fileColumn)
|
99
|
+
else
|
100
|
+
displayFailedUpload(progressColumn)
|
101
|
+
console.log $("Message", xhr.responseXML).text()
|
102
|
+
|
103
|
+
xhr.open "POST", endpoint, true
|
104
|
+
xhr.send formData
|
105
|
+
error: (xhr) ->
|
106
|
+
displayFailedUpload()
|
107
|
+
console.log xhr.responseText
|
108
|
+
|
109
|
+
jQuery ->
|
110
|
+
|
111
|
+
$(document).on "change", ".s3r-field", ->
|
112
|
+
$this = $(this)
|
113
|
+
|
114
|
+
if !!window.FormData
|
115
|
+
uploadFiles($this.parent())
|
116
|
+
else
|
117
|
+
$this.hide()
|
118
|
+
$this.parent().append("<p>Your browser can't handle file uploads, please switch to <a href='http://google.com/chrome'>Google Chrome</a>.</p>")
|
@@ -0,0 +1,31 @@
|
|
1
|
+
.s3r-upload-list {
|
2
|
+
border: 1px solid #ccc;
|
3
|
+
border-collapse: collapse;
|
4
|
+
margin: 15px 0;
|
5
|
+
}
|
6
|
+
|
7
|
+
.s3r-upload-list td {
|
8
|
+
border-bottom: 1px solid #ccc;
|
9
|
+
padding: .5em;
|
10
|
+
width: 180px;
|
11
|
+
}
|
12
|
+
|
13
|
+
.s3r-file-url {
|
14
|
+
overflow: hidden;
|
15
|
+
text-overflow: ellipsis;
|
16
|
+
white-space: nowrap;
|
17
|
+
width: 180px;
|
18
|
+
}
|
19
|
+
|
20
|
+
.s3r-bar {
|
21
|
+
background-color: #eee;
|
22
|
+
border-radius: 3px;
|
23
|
+
height: 15px;
|
24
|
+
width: 180px;
|
25
|
+
}
|
26
|
+
|
27
|
+
.s3r-meter {
|
28
|
+
background-color: #43ac6a;
|
29
|
+
border-radius: 3px;
|
30
|
+
height: 15px;
|
31
|
+
}
|