mongoid-direct-s3-upload 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.
- 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
|
+
}
|