s3_relay 0.0.2
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 +209 -0
- data/Rakefile +31 -0
- data/app/assets/javascripts/s3_relay.coffee +112 -0
- data/app/assets/stylesheets/s3_relay.css +31 -0
- data/app/controllers/s3_relay/uploads_controller.rb +65 -0
- data/app/helpers/s3_relay/uploads_helper.rb +23 -0
- data/app/models/s3_relay/upload.rb +46 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20141009000804_create_s3_relay_uploads.rb +19 -0
- data/lib/s3_relay.rb +4 -0
- data/lib/s3_relay/base.rb +35 -0
- data/lib/s3_relay/engine.rb +18 -0
- data/lib/s3_relay/model.rb +54 -0
- data/lib/s3_relay/private_url.rb +33 -0
- data/lib/s3_relay/s3_relay.rb +4 -0
- data/lib/s3_relay/upload_presigner.rb +60 -0
- data/lib/s3_relay/version.rb +3 -0
- data/test/controllers/s3_relay/uploads_controller_test.rb +138 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/product.rb +6 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +78 -0
- data/test/dummy/config/environments/test.rb +39 -0
- data/test/dummy/config/initializers/assets.rb +8 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -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/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +7 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/db/migrate/20141021002149_create_products.rb +9 -0
- data/test/dummy/db/schema.rb +41 -0
- data/test/dummy/log/development.log +53 -0
- data/test/dummy/log/test.log +14631 -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/favicon.ico +0 -0
- data/test/factories/products.rb +5 -0
- data/test/factories/uploads.rb +15 -0
- data/test/helpers/s3_relay/uploads_helper_test.rb +13 -0
- data/test/lib/s3_relay/model_test.rb +81 -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 +128 -0
- data/test/support/database_cleaner.rb +14 -0
- data/test/test_helper.rb +28 -0
- metadata +302 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dcccfe11a546093475043762594d3df9af34b075
|
4
|
+
data.tar.gz: 79acd9bdd1b6b6a6877341a08b9ec5c923fb3f3d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 504414953661b8a7fc6bc069fe90eef08005f1fdd0d5a3918a585aa132f684a831bee1c286475c722d129d331890b0545eb0d5e601c9fd3f3a35e94c94a265e2
|
7
|
+
data.tar.gz: 94578b8eec4bbe0a24855359d0098aaac6bbbebcf8e239d9a3b811ab98fe973eef0b37d625be722d5cf7dd56e55a99c5f76ac8adc2300f3ecebd7375784b87f5
|
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,209 @@
|
|
1
|
+
# s3_relay
|
2
|
+
|
3
|
+
Enables direct file uploads to Amazon S3 and provides a flexible pattern for
|
4
|
+
your Rails app to asynchronously ingest the files.
|
5
|
+
|
6
|
+
## Overview
|
7
|
+
|
8
|
+
This Rails engine allows you to quickly implement direct uploads to Amazon S3
|
9
|
+
from your Rails 3.1+ / 4.x application. It does not depend on any specific file
|
10
|
+
upload libraries, UI frameworks or AWS gems, like other solutions tend to.
|
11
|
+
|
12
|
+
It works by utilizing Amazon S3's
|
13
|
+
[Cross-Origin Resource Sharing](http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html)
|
14
|
+
to permit browser-based uploads directly to S3 with presigned URLs generated by
|
15
|
+
this gem with your application's API credentials. As each file is uploaded,
|
16
|
+
the gem persists detail about the uploaded file in your application's database.
|
17
|
+
This table should be thought of much like a queue - think
|
18
|
+
[DelayedJob](https://github.com/collectiveidea/delayed_job) for your
|
19
|
+
uploaded-but-not-yet-ingested file uploads.
|
20
|
+
|
21
|
+
How (and if) you choose to import each uploaded file into your processing
|
22
|
+
library of choice is completely up to you. The gem tracks the state of each
|
23
|
+
upload so that you may used the provided `.pending` scope and `mark_imported!`
|
24
|
+
method to fetch, process (via your background processor of choice), then
|
25
|
+
mark-off each upload record whose file has been successfully ingested by your
|
26
|
+
app.
|
27
|
+
|
28
|
+
## Features
|
29
|
+
|
30
|
+
* Files can be uploaded before or after your parent object has been saved.
|
31
|
+
* File upload fields can be placed inside or outside of your parent object's
|
32
|
+
form.
|
33
|
+
* File uploads display completion progress.
|
34
|
+
* Boilerplate styling can be used or easily replaced.
|
35
|
+
* All uploads are set to private by default, however support for other ACLs
|
36
|
+
is in the plans.
|
37
|
+
* 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
|
38
|
+
write to disk. S3 then decrypts and streams the files as they are downloaded.
|
39
|
+
* Models can have multiple types of uploads and specify for each if only one
|
40
|
+
file is permitted or if multiple are.
|
41
|
+
* Multiple files can upload concurrently to S3.
|
42
|
+
|
43
|
+
## Technology & Requirements
|
44
|
+
|
45
|
+
Uploads are made possible by use of the `FormData` object, defined in
|
46
|
+
[XMLHttpRequest Level 2](http://dev.w3.org/2006/webapi/XMLHttpRequest-2/).
|
47
|
+
Many people are broadly referring to this as being provided by HTML5, but
|
48
|
+
technically it's part of the aforementioned spec that browsers have been
|
49
|
+
adhering to for a couple of major versions now. Even IE, wuh?
|
50
|
+
|
51
|
+
The latest versions of all of the following are ideal, but here are the gem's
|
52
|
+
minimum requirements:
|
53
|
+
|
54
|
+
* Ruby 1.9.3+
|
55
|
+
* Rails 3.1+
|
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 a 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 "s3_relay"` 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
|
+
```
|
95
|
+
S3_RELAY_ACCESS_KEY_ID="abc123"
|
96
|
+
S3_RELAY_SECRET_ACCESS_KEY="xzy456"
|
97
|
+
S3_RELAY_REGION="us-west-2"
|
98
|
+
S3_RELAY_BUCKET="some-s3-bucket"
|
99
|
+
S3_RELAY_ACL="private"
|
100
|
+
```
|
101
|
+
|
102
|
+
## Use
|
103
|
+
|
104
|
+
### Add upload definitions to your model
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class Product < ActiveRecord::Base
|
108
|
+
s3_relay :icon_upload
|
109
|
+
s3_relay :photo_uploads, has_many: true
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
### Restricting uploads to authenticated users
|
114
|
+
|
115
|
+
If your app's file uploads need to be restricted to logged in users, simply
|
116
|
+
override the following method in your application controller to call any
|
117
|
+
authentication method you're currently using.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
def authenticate_for_s3_relay
|
121
|
+
authenticate_user! # Devise example
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
### Add virtual attributes to your controller's Strong Parameters config
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
product_params = params.require(:product)
|
129
|
+
.permit(:name, :new_icon_upload_uuid, new_photo_uploads_uuids: [])
|
130
|
+
|
131
|
+
@product = Product.new(product_params)
|
132
|
+
```
|
133
|
+
|
134
|
+
### Add file upload fields to your views
|
135
|
+
|
136
|
+
```erb
|
137
|
+
<%= s3_relay_field @product, :icon_upload %>
|
138
|
+
<%= s3_relay_field @product, :photo_uploads, multiple: true %>
|
139
|
+
```
|
140
|
+
|
141
|
+
### Process uploads asynchronously
|
142
|
+
|
143
|
+
Use your background job processor of choice to process uploads pending
|
144
|
+
ingestion (and image processing) by your app.
|
145
|
+
|
146
|
+
Say you're using [Resque](https://github.com/resque/resque) and [CarrierWave](https://github.com/carrierwaveuploader/carrierwave), you could define a job class:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class ProductPhotoImporter
|
150
|
+
@queue = :photo_import
|
151
|
+
|
152
|
+
def self.perform(product_id)
|
153
|
+
@product = Product.find(id)
|
154
|
+
|
155
|
+
@product.photo_uploads.pending.each do |upload|
|
156
|
+
@product.photos.create(remote_file_url: upload.private_url)
|
157
|
+
upload.mark_processed!
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
Then, enqueue a job from your controller, callback, service object, etc.
|
164
|
+
whenever there may be new uploads to process:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
Resque.enqueue(ProductPhotoImporter, product.id)
|
168
|
+
```
|
169
|
+
|
170
|
+
### Restricting the objects uploads can be associated with
|
171
|
+
|
172
|
+
Remember the time when that guy found a way to a submit Github form in such a
|
173
|
+
way that it linked a new SSH key he provided to DHH's user record? No bueno.
|
174
|
+
Don't let your users attach files to objects they don't have access to.
|
175
|
+
|
176
|
+
You can prevent this by defining a method in ApplicationController that
|
177
|
+
filters out the parent object params passed during upload creation if your logic
|
178
|
+
finds that the user doesn't have access to the parent object in question. Ex:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
def order_file_uploads_params(parent)
|
182
|
+
if parent.user == current_user
|
183
|
+
# Yep, that's your order, you can add files to it
|
184
|
+
{ parent: parent }
|
185
|
+
else
|
186
|
+
# Nope, you're trying to add a file to someone else's order, or whatever
|
187
|
+
{ }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
## Contributing
|
193
|
+
|
194
|
+
1. [Fork it](https://github.com/kjohnston/s3_relay/fork)
|
195
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
196
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
197
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
198
|
+
5. [Create a Pull Request](https://github.com/kjohnston/s3_relay/pull/new)
|
199
|
+
|
200
|
+
## Contributors
|
201
|
+
|
202
|
+
Many thanks go to the following who have contributed to making this gem even better:
|
203
|
+
|
204
|
+
[your name here]
|
205
|
+
|
206
|
+
## License
|
207
|
+
|
208
|
+
* Freely distributable and licensed under the [MIT license](http://kjohnston.mit-license.org/license.html).
|
209
|
+
* 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,112 @@
|
|
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
|
+
saveUrl = (container, uuid, filename, contentType, publicUrl) ->
|
8
|
+
privateUrl = null
|
9
|
+
|
10
|
+
$.ajax
|
11
|
+
type: "POST"
|
12
|
+
url: "/s3_relay/uploads"
|
13
|
+
async: false
|
14
|
+
data:
|
15
|
+
parent_type: container.data("parentType")
|
16
|
+
parent_id: container.data("parentId")
|
17
|
+
association: container.data("association")
|
18
|
+
uuid: uuid
|
19
|
+
filename: filename
|
20
|
+
content_type: contentType
|
21
|
+
public_url: publicUrl
|
22
|
+
success: (data, status, xhr) ->
|
23
|
+
privateUrl = data.private_url
|
24
|
+
error: (xhr) ->
|
25
|
+
console.log xhr.responseText
|
26
|
+
|
27
|
+
return privateUrl
|
28
|
+
|
29
|
+
uploadFiles = (container) ->
|
30
|
+
fileInput = $("input.s3r-field", container)
|
31
|
+
files = fileInput.get(0).files
|
32
|
+
uploadFile(container, file) for file in files
|
33
|
+
fileInput.val("")
|
34
|
+
|
35
|
+
uploadFile = (container, file) ->
|
36
|
+
fileName = file.name
|
37
|
+
|
38
|
+
$.ajax
|
39
|
+
type: "GET"
|
40
|
+
url: "/s3_relay/uploads/new"
|
41
|
+
async: false
|
42
|
+
success: (data, status, xhr) ->
|
43
|
+
formData = new FormData()
|
44
|
+
xhr = new XMLHttpRequest()
|
45
|
+
endpoint = data.endpoint
|
46
|
+
|
47
|
+
formData.append("AWSAccessKeyID", data.awsaccesskeyid)
|
48
|
+
formData.append("x-amz-server-side-encryption", data.x_amz_server_side_encryption)
|
49
|
+
formData.append("key", data.key)
|
50
|
+
formData.append("success_action_status", data.success_action_status)
|
51
|
+
formData.append("acl", data.acl)
|
52
|
+
formData.append("policy", data.policy)
|
53
|
+
formData.append("signature", data.signature)
|
54
|
+
formData.append("content-type", file.type)
|
55
|
+
formData.append("file", file)
|
56
|
+
|
57
|
+
uuid = data.uuid
|
58
|
+
|
59
|
+
uploadList = $(".s3r-upload-list", container)
|
60
|
+
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>")
|
61
|
+
fileColumn = $(".s3r-upload-list ##{uuid} .s3r-file-url", container)
|
62
|
+
progressColumn = $(".s3r-upload-list ##{uuid} .s3r-progress", container)
|
63
|
+
progressBar = $(".s3r-bar", progressColumn)
|
64
|
+
progressMeter = $(".s3r-meter", progressColumn)
|
65
|
+
|
66
|
+
xhr.upload.addEventListener "progress", (ev) ->
|
67
|
+
if ev.position
|
68
|
+
percentage = ((ev.position / ev.totalSize) * 100.0).toFixed(0)
|
69
|
+
progressBar.show()
|
70
|
+
progressMeter.css "width", "#{percentage}%"
|
71
|
+
else
|
72
|
+
progressColumn.text("Uploading...") # IE can't get position
|
73
|
+
|
74
|
+
xhr.onreadystatechange = (ev) ->
|
75
|
+
if xhr.readyState is 4
|
76
|
+
progressColumn.text("") # IE can't get position
|
77
|
+
progressBar.remove()
|
78
|
+
|
79
|
+
if xhr.status == 201
|
80
|
+
contentType = file.type
|
81
|
+
publicUrl = $("Location", xhr.responseXML).text()
|
82
|
+
privateUrl = saveUrl(container, uuid, fileName, contentType, publicUrl)
|
83
|
+
|
84
|
+
if privateUrl == null
|
85
|
+
displayFailedUpload(progressColumn)
|
86
|
+
else
|
87
|
+
fileColumn.html("<a href='#{privateUrl}'>#{fileName}</a>")
|
88
|
+
|
89
|
+
virtualAttr = "#{container.data('parentType')}[new_#{container.data('association')}_uuids]"
|
90
|
+
hiddenField = "<input type='hidden' name='#{virtualAttr}[]' value='#{uuid}' />"
|
91
|
+
container.append(hiddenField)
|
92
|
+
|
93
|
+
else
|
94
|
+
displayFailedUpload(progressColumn)
|
95
|
+
console.log $("Message", xhr.responseXML).text()
|
96
|
+
|
97
|
+
xhr.open "POST", endpoint, true
|
98
|
+
xhr.send formData
|
99
|
+
error: (xhr) ->
|
100
|
+
displayFailedUpload()
|
101
|
+
console.log xhr.responseText
|
102
|
+
|
103
|
+
jQuery ->
|
104
|
+
|
105
|
+
$(document).on "change", ".s3r-field", ->
|
106
|
+
$this = $(this)
|
107
|
+
|
108
|
+
if !!window.FormData
|
109
|
+
uploadFiles($this.parent())
|
110
|
+
else
|
111
|
+
$this.hide()
|
112
|
+
$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
|
+
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class S3Relay::UploadsController < ApplicationController
|
2
|
+
|
3
|
+
before_filter :authenticate
|
4
|
+
skip_before_filter :verify_authenticity_token
|
5
|
+
|
6
|
+
def new
|
7
|
+
render json: S3Relay::UploadPresigner.new.form_data
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
@upload = S3Relay::Upload.new(upload_attrs)
|
12
|
+
|
13
|
+
if @upload.save
|
14
|
+
render json: { private_url: @upload.private_url }, status: 201
|
15
|
+
else
|
16
|
+
render json: { errors: @upload.errors }, status: 422
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def authenticate
|
23
|
+
if respond_to?(:authenticate_for_s3_relay)
|
24
|
+
authenticate_for_s3_relay
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def parent_attrs
|
29
|
+
type = params[:parent_type].try(:classify)
|
30
|
+
id = params[:parent_id]
|
31
|
+
|
32
|
+
return {} unless type.present? && id.present? &&
|
33
|
+
parent = type.constantize.find_by_id(id)
|
34
|
+
|
35
|
+
begin
|
36
|
+
public_send(
|
37
|
+
"#{type.underscore.downcase}_#{params[:association]}_params",
|
38
|
+
parent
|
39
|
+
)
|
40
|
+
rescue NoMethodError
|
41
|
+
{ parent: parent }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def upload_attrs
|
46
|
+
attrs = {
|
47
|
+
upload_type: params[:association].try(:classify),
|
48
|
+
uuid: params[:uuid],
|
49
|
+
filename: params[:filename],
|
50
|
+
content_type: params[:content_type]
|
51
|
+
}
|
52
|
+
|
53
|
+
attrs.merge!(parent_attrs)
|
54
|
+
attrs.merge!(user_attrs)
|
55
|
+
end
|
56
|
+
|
57
|
+
def user_attrs
|
58
|
+
if respond_to?(:current_user) && (id = current_user.try(:id))
|
59
|
+
{ user_id: id }
|
60
|
+
else
|
61
|
+
{}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|