refile 0.2.2
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/.gitignore +27 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +476 -0
- data/Rakefile +11 -0
- data/app/assets/javascripts/refile.js +50 -0
- data/app/helpers/attachment_helper.rb +52 -0
- data/config.ru +8 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +3 -0
- data/lib/refile.rb +72 -0
- data/lib/refile/app.rb +97 -0
- data/lib/refile/attachment.rb +89 -0
- data/lib/refile/attachment/active_record.rb +24 -0
- data/lib/refile/backend/file_system.rb +70 -0
- data/lib/refile/backend/s3.rb +129 -0
- data/lib/refile/file.rb +65 -0
- data/lib/refile/image_processing.rb +73 -0
- data/lib/refile/rails.rb +36 -0
- data/lib/refile/random_hasher.rb +5 -0
- data/lib/refile/version.rb +3 -0
- data/refile.gemspec +34 -0
- data/spec/refile/app_spec.rb +151 -0
- data/spec/refile/attachment_spec.rb +141 -0
- data/spec/refile/backend/file_system_spec.rb +30 -0
- data/spec/refile/backend/s3_spec.rb +11 -0
- data/spec/refile/backend_examples.rb +215 -0
- data/spec/refile/features/direct_upload_spec.rb +29 -0
- data/spec/refile/features/normal_upload_spec.rb +36 -0
- data/spec/refile/features/presigned_upload_spec.rb +29 -0
- data/spec/refile/fixtures/hello.txt +1 -0
- data/spec/refile/fixtures/large.txt +44 -0
- data/spec/refile/spec_helper.rb +58 -0
- data/spec/refile/test_app.rb +46 -0
- data/spec/refile/test_app/app/assets/javascripts/application.js +40 -0
- data/spec/refile/test_app/app/controllers/application_controller.rb +2 -0
- data/spec/refile/test_app/app/controllers/direct_posts_controller.rb +15 -0
- data/spec/refile/test_app/app/controllers/home_controller.rb +4 -0
- data/spec/refile/test_app/app/controllers/normal_posts_controller.rb +19 -0
- data/spec/refile/test_app/app/controllers/presigned_posts_controller.rb +30 -0
- data/spec/refile/test_app/app/models/post.rb +5 -0
- data/spec/refile/test_app/app/views/direct_posts/new.html.erb +16 -0
- data/spec/refile/test_app/app/views/home/index.html.erb +1 -0
- data/spec/refile/test_app/app/views/layouts/application.html.erb +14 -0
- data/spec/refile/test_app/app/views/normal_posts/new.html.erb +20 -0
- data/spec/refile/test_app/app/views/normal_posts/show.html.erb +9 -0
- data/spec/refile/test_app/app/views/presigned_posts/new.html.erb +16 -0
- data/spec/refile/test_app/config/database.yml +7 -0
- data/spec/refile/test_app/config/routes.rb +17 -0
- data/spec/refile/test_app/public/favicon.ico +0 -0
- data/spec/refile_spec.rb +35 -0
- metadata +294 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
require "refile/test_app"
|
2
|
+
|
3
|
+
feature "Direct HTTP post file uploads", :js do
|
4
|
+
scenario "Successfully upload a file" do
|
5
|
+
visit "/presigned/posts/new"
|
6
|
+
fill_in "Title", with: "A cool post"
|
7
|
+
attach_file "Document", path("hello.txt")
|
8
|
+
|
9
|
+
expect(page).to have_content("Upload started")
|
10
|
+
expect(page).to have_content("Upload complete token accepted")
|
11
|
+
expect(page).to have_content("Upload success token accepted")
|
12
|
+
|
13
|
+
click_button "Create"
|
14
|
+
|
15
|
+
expect(page).to have_selector("h1", text: "A cool post")
|
16
|
+
result = Net::HTTP.get_response(URI(find_link("Document")[:href])).body.chomp
|
17
|
+
expect(result).to eq("hello")
|
18
|
+
end
|
19
|
+
|
20
|
+
scenario "Fail to upload a file that is too large" do
|
21
|
+
visit "/presigned/posts/new"
|
22
|
+
fill_in "Title", with: "A cool post"
|
23
|
+
attach_file "Document", path("large.txt")
|
24
|
+
|
25
|
+
expect(page).to have_content("Upload started")
|
26
|
+
expect(page).to have_content("Upload failure too large")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
hello
|
@@ -0,0 +1,44 @@
|
|
1
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec massa arcu,
|
2
|
+
convallis a tellus vitae, placerat rhoncus nisi. Nullam molestie volutpat
|
3
|
+
turpis vitae viverra. Integer nulla nisl, dictum et rutrum quis, auctor ut dui.
|
4
|
+
Nulla imperdiet ante a lorem molestie tempor. Suspendisse pharetra in mauris
|
5
|
+
mollis dapibus. Nullam pretium fringilla justo, faucibus condimentum elit
|
6
|
+
rutrum ac. Vivamus sem justo, congue id lobortis nec, viverra ac lacus. Donec
|
7
|
+
eu vestibulum risus, at efficitur nisl. Maecenas sed elit a dui malesuada
|
8
|
+
ultricies at eget est. Duis lobortis tincidunt pellentesque.
|
9
|
+
|
10
|
+
Quisque in nisl felis. Quisque a neque nec diam posuere mattis. Donec a nibh
|
11
|
+
cursus, tempus est iaculis, gravida odio. Pellentesque eleifend enim eget
|
12
|
+
placerat dignissim. Cum sociis natoque penatibus et magnis dis parturient
|
13
|
+
montes, nascetur ridiculus mus. In non ex in augue feugiat convallis. Duis
|
14
|
+
varius at sapien vitae molestie. Suspendisse sit amet aliquet quam, quis
|
15
|
+
condimentum nibh.
|
16
|
+
|
17
|
+
Donec a quam quis ipsum imperdiet semper. In ac scelerisque elit, non fermentum
|
18
|
+
nisl. Nulla dapibus velit eget ullamcorper blandit. Maecenas justo diam,
|
19
|
+
porttitor eget nunc dictum, sollicitudin lacinia lectus. Pellentesque non augue
|
20
|
+
urna. In felis ipsum, posuere vitae sapien non, aliquet maximus nibh.
|
21
|
+
Suspendisse potenti. Aenean venenatis euismod congue. Praesent quis ullamcorper
|
22
|
+
sapien. In hac habitasse platea dictumst. Morbi semper augue dapibus, posuere
|
23
|
+
justo sit amet, convallis felis. Vivamus condimentum elementum ex, quis rhoncus
|
24
|
+
purus pulvinar et. Nunc vel risus sem. Suspendisse porttitor convallis massa,
|
25
|
+
molestie sollicitudin metus semper a. Donec ac cursus tortor. Nam felis nulla,
|
26
|
+
pretium eu tempus at, euismod eu erat.
|
27
|
+
|
28
|
+
Donec vel tempus augue. Pellentesque sit amet ante in odio malesuada facilisis
|
29
|
+
nec sit amet turpis. Donec vitae iaculis mauris. Aenean venenatis interdum
|
30
|
+
quam, nec tincidunt mauris aliquam vel. Nunc ultrices arcu euismod velit
|
31
|
+
ultricies, id laoreet arcu venenatis. Donec euismod scelerisque magna, nec
|
32
|
+
interdum lorem ornare nec. Morbi blandit volutpat velit, consequat maximus
|
33
|
+
justo mattis non. In pellentesque malesuada consectetur. Cras convallis mi ut
|
34
|
+
nibh rutrum ullamcorper. Nam finibus consequat erat a feugiat. Donec laoreet
|
35
|
+
risus eget enim interdum dictum. Duis orci lectus, scelerisque tempus volutpat
|
36
|
+
eget, ullamcorper eu felis. Aliquam tempor in dui sit amet dapibus. Nulla sed
|
37
|
+
metus vestibulum, dignissim odio et, dapibus eros. Praesent semper arcu ut
|
38
|
+
augue suscipit, ac ornare tortor auctor. Suspendisse a velit dui.
|
39
|
+
|
40
|
+
Etiam nec est ut ex laoreet iaculis vel id enim. Vivamus ac hendrerit leo.
|
41
|
+
Vestibulum auctor nibh nec arcu rhoncus, a condimentum quam accumsan. Ut justo
|
42
|
+
augue, laoreet at bibendum vel, aliquet vitae est. Aliquam accumsan ac diam nec
|
43
|
+
pretium. Nulla dictum velit nec elementum mollis. Interdum et malesuada fames
|
44
|
+
ac ante ipsum primis in faucibus.
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "pry"
|
2
|
+
require "refile"
|
3
|
+
require "refile/backend_examples"
|
4
|
+
|
5
|
+
tmp_path = Dir.mktmpdir
|
6
|
+
|
7
|
+
at_exit do
|
8
|
+
FileUtils.remove_entry_secure(tmp_path)
|
9
|
+
end
|
10
|
+
|
11
|
+
Refile.store = Refile::Backend::FileSystem.new(File.expand_path("default_store", tmp_path))
|
12
|
+
Refile.cache = Refile::Backend::FileSystem.new(File.expand_path("default_cache", tmp_path))
|
13
|
+
|
14
|
+
class FakePresignBackend < Refile::Backend::FileSystem
|
15
|
+
Signature = Struct.new(:as, :id, :url, :fields)
|
16
|
+
|
17
|
+
def presign
|
18
|
+
id = Refile::RandomHasher.new.hash
|
19
|
+
Signature.new("file", id, "/presigned/posts/upload", { token: "xyz123", id: id })
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Refile.backends["limited_cache"] = FakePresignBackend.new(File.expand_path("default_cache", tmp_path), max_size: 100)
|
24
|
+
|
25
|
+
Refile.direct_upload = ["cache", "limited_cache"]
|
26
|
+
|
27
|
+
class Refile::FileDouble
|
28
|
+
def initialize(data)
|
29
|
+
@io = StringIO.new(data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def read(*args)
|
33
|
+
@io.read(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def size
|
37
|
+
@io.size
|
38
|
+
end
|
39
|
+
|
40
|
+
def eof?
|
41
|
+
@io.eof?
|
42
|
+
end
|
43
|
+
|
44
|
+
def close
|
45
|
+
@io.close
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module PathHelper
|
50
|
+
def path(filename)
|
51
|
+
File.expand_path(File.join("fixtures", filename), File.dirname(__FILE__))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
RSpec.configure do |config|
|
56
|
+
config.include PathHelper
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "rails/all"
|
2
|
+
|
3
|
+
require "refile"
|
4
|
+
require "refile/rails"
|
5
|
+
require "jquery/rails"
|
6
|
+
|
7
|
+
module Refile
|
8
|
+
class TestApp < Rails::Application
|
9
|
+
config.secret_token = '6805012ab1750f461ef3c531bdce84c0'
|
10
|
+
config.session_store :cookie_store, :key => '_refile_session'
|
11
|
+
config.active_support.deprecation = :log
|
12
|
+
config.eager_load = false
|
13
|
+
config.action_dispatch.show_exceptions = false
|
14
|
+
config.consider_all_requests_local = true
|
15
|
+
config.root = ::File.expand_path("test_app", ::File.dirname(__FILE__))
|
16
|
+
end
|
17
|
+
|
18
|
+
Rails.backtrace_cleaner.remove_silencers!
|
19
|
+
TestApp.initialize!
|
20
|
+
end
|
21
|
+
|
22
|
+
class TestMigration < ActiveRecord::Migration
|
23
|
+
def self.up
|
24
|
+
create_table :posts, :force => true do |t|
|
25
|
+
t.column :title, :string
|
26
|
+
t.column :image_id, :string
|
27
|
+
t.column :document_id, :string
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
quietly do
|
33
|
+
TestMigration.up
|
34
|
+
end
|
35
|
+
|
36
|
+
require "rspec"
|
37
|
+
require "rspec/rails"
|
38
|
+
require "capybara/rails"
|
39
|
+
require "capybara/rspec"
|
40
|
+
require "refile/spec_helper"
|
41
|
+
|
42
|
+
Capybara.configure do |config|
|
43
|
+
config.server_port = 56120
|
44
|
+
end
|
45
|
+
|
46
|
+
Refile.host = "//127.0.0.1:56120"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
//= require jquery
|
2
|
+
//= require refile
|
3
|
+
|
4
|
+
"use strict";
|
5
|
+
|
6
|
+
document.addEventListener("DOMContentLoaded", function() {
|
7
|
+
var form = document.querySelector("form#direct");
|
8
|
+
|
9
|
+
if(form) {
|
10
|
+
var input = document.querySelector("#post_document");
|
11
|
+
|
12
|
+
form.addEventListener("upload:start", function() {
|
13
|
+
var p = document.createElement("p");
|
14
|
+
p.textContent = "Upload started";
|
15
|
+
form.appendChild(p);
|
16
|
+
});
|
17
|
+
|
18
|
+
form.addEventListener("upload:complete", function(e) {
|
19
|
+
var p = document.createElement("p");
|
20
|
+
p.textContent = "Upload complete " + e.detail;
|
21
|
+
form.appendChild(p);
|
22
|
+
});
|
23
|
+
|
24
|
+
form.addEventListener("upload:progress", function(e) {
|
25
|
+
var p = document.createElement("p");
|
26
|
+
p.textContent = "Upload progress " + e.detail.loaded + " " + e.detail.total;
|
27
|
+
form.appendChild(p);
|
28
|
+
});
|
29
|
+
|
30
|
+
form.addEventListener("upload:failure", function(e) {
|
31
|
+
var p = document.createElement("p");
|
32
|
+
p.textContent = "Upload failure " + e.detail
|
33
|
+
form.appendChild(p);
|
34
|
+
});
|
35
|
+
}
|
36
|
+
});
|
37
|
+
|
38
|
+
$(document).on("upload:success", "form#direct", function(e) {
|
39
|
+
$("<p></p>").text("Upload success " + e.originalEvent.detail).appendTo(this);
|
40
|
+
});
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class DirectPostsController < ApplicationController
|
2
|
+
def new
|
3
|
+
@post = Post.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def create
|
7
|
+
@post = Post.new(params.require(:post).permit(:title, :document_cache_id))
|
8
|
+
|
9
|
+
if @post.save
|
10
|
+
redirect_to [:normal, @post]
|
11
|
+
else
|
12
|
+
render :new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class NormalPostsController < ApplicationController
|
2
|
+
def new
|
3
|
+
@post = Post.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def show
|
7
|
+
@post = Post.find(params[:id])
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
@post = Post.new(params.require(:post).permit(:title, :image, :image_cache_id, :document, :document_cache_id))
|
12
|
+
|
13
|
+
if @post.save
|
14
|
+
redirect_to [:normal, @post]
|
15
|
+
else
|
16
|
+
render :new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class PresignedPostsController < ApplicationController
|
2
|
+
def new
|
3
|
+
@post = Post.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def create
|
7
|
+
@post = Post.new(params.require(:post).permit(:title, :document_cache_id))
|
8
|
+
|
9
|
+
if @post.save
|
10
|
+
redirect_to [:normal, @post]
|
11
|
+
else
|
12
|
+
render :new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def upload
|
17
|
+
if params[:token] == "xyz123"
|
18
|
+
if params[:file].size < 100
|
19
|
+
File.open(File.join(Refile.backends["limited_cache"].directory, params[:id]), "wb") do |file|
|
20
|
+
file.write(params[:file].read)
|
21
|
+
end
|
22
|
+
render text: "token accepted"
|
23
|
+
else
|
24
|
+
render text: "too large", status: 413
|
25
|
+
end
|
26
|
+
else
|
27
|
+
render text: "token rejected", status: 403
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= form_for [:direct, @post], html: { id: "direct" } do |form| %>
|
2
|
+
<p>
|
3
|
+
<%= @post.errors.full_messages.to_sentence %>
|
4
|
+
</p>
|
5
|
+
<p>
|
6
|
+
<%= form.label :title %>
|
7
|
+
<%= form.text_field :title %>
|
8
|
+
</p>
|
9
|
+
<p>
|
10
|
+
<%= form.label :document %>
|
11
|
+
<%= form.attachment_field :document, direct: true %>
|
12
|
+
</p>
|
13
|
+
<p>
|
14
|
+
<%= form.submit "Create" %>
|
15
|
+
</p>
|
16
|
+
<% end %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Hello world</h1>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<%= form_for [:normal, @post] do |form| %>
|
2
|
+
<p>
|
3
|
+
<%= @post.errors.full_messages.to_sentence %>
|
4
|
+
</p>
|
5
|
+
<p>
|
6
|
+
<%= form.label :title %>
|
7
|
+
<%= form.text_field :title %>
|
8
|
+
</p>
|
9
|
+
<p>
|
10
|
+
<%= form.label :image %>
|
11
|
+
<%= form.attachment_field :image %>
|
12
|
+
</p>
|
13
|
+
<p>
|
14
|
+
<%= form.label :document %>
|
15
|
+
<%= form.attachment_field :document %>
|
16
|
+
</p>
|
17
|
+
<p>
|
18
|
+
<%= form.submit "Create" %>
|
19
|
+
</p>
|
20
|
+
<% end %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= form_for [:presigned, @post], html: { id: "direct" } do |form| %>
|
2
|
+
<p>
|
3
|
+
<%= @post.errors.full_messages.to_sentence %>
|
4
|
+
</p>
|
5
|
+
<p>
|
6
|
+
<%= form.label :title %>
|
7
|
+
<%= form.text_field :title %>
|
8
|
+
</p>
|
9
|
+
<p>
|
10
|
+
<%= form.label :document %>
|
11
|
+
<%= form.attachment_field :document, presigned: true %>
|
12
|
+
</p>
|
13
|
+
<p>
|
14
|
+
<%= form.submit "Create" %>
|
15
|
+
</p>
|
16
|
+
<% end %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Refile::TestApp.routes.draw do
|
2
|
+
root to: "home#index"
|
3
|
+
|
4
|
+
scope path: "normal", as: "normal" do
|
5
|
+
resources :posts, only: [:new, :create, :show], controller: "normal_posts"
|
6
|
+
end
|
7
|
+
|
8
|
+
scope path: "direct", as: "direct" do
|
9
|
+
resources :posts, only: [:new, :create], controller: "direct_posts"
|
10
|
+
end
|
11
|
+
|
12
|
+
scope path: "presigned", as: "presigned" do
|
13
|
+
resources :posts, only: [:new, :create], controller: "presigned_posts" do
|
14
|
+
post :upload, on: :collection
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
File without changes
|
data/spec/refile_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require "refile"
|
2
|
+
|
3
|
+
RSpec.describe Refile do
|
4
|
+
let(:io) { StringIO.new("hello") }
|
5
|
+
|
6
|
+
describe ".verify_uploadable" do
|
7
|
+
it "works if it conforms to required API" do
|
8
|
+
expect(Refile.verify_uploadable(double(size: 444, read: io, eof?: true, close: nil), nil)).to be_truthy
|
9
|
+
end
|
10
|
+
|
11
|
+
it "raises ArgumentError if argument does not respond to `size`" do
|
12
|
+
expect { Refile.verify_uploadable(double(read: io, eof?: true, close: nil), nil) }.to raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises ArgumentError if argument does not respond to `read`" do
|
16
|
+
expect { Refile.verify_uploadable(double(size: 444, eof?: true, close: nil), nil) }.to raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "raises ArgumentError if argument does not respond to `eof?`" do
|
20
|
+
expect { Refile.verify_uploadable(double(size: 444, read: true, close: nil), nil) }.to raise_error(ArgumentError)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "raises ArgumentError if argument does not respond to `close`" do
|
24
|
+
expect { Refile.verify_uploadable(double(size: 444, read: true, eof?: true), nil) }.to raise_error(ArgumentError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns true if size is respeced" do
|
28
|
+
expect(Refile.verify_uploadable(Refile::FileDouble.new("hello"), 8)).to be_truthy
|
29
|
+
end
|
30
|
+
|
31
|
+
it "raises Refile::Invalid if size is exceeded" do
|
32
|
+
expect { Refile.verify_uploadable(Refile::FileDouble.new("hello world"), 8) }.to raise_error(Refile::Invalid)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|