refile 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +8 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +476 -0
  8. data/Rakefile +11 -0
  9. data/app/assets/javascripts/refile.js +50 -0
  10. data/app/helpers/attachment_helper.rb +52 -0
  11. data/config.ru +8 -0
  12. data/config/locales/en.yml +5 -0
  13. data/config/routes.rb +3 -0
  14. data/lib/refile.rb +72 -0
  15. data/lib/refile/app.rb +97 -0
  16. data/lib/refile/attachment.rb +89 -0
  17. data/lib/refile/attachment/active_record.rb +24 -0
  18. data/lib/refile/backend/file_system.rb +70 -0
  19. data/lib/refile/backend/s3.rb +129 -0
  20. data/lib/refile/file.rb +65 -0
  21. data/lib/refile/image_processing.rb +73 -0
  22. data/lib/refile/rails.rb +36 -0
  23. data/lib/refile/random_hasher.rb +5 -0
  24. data/lib/refile/version.rb +3 -0
  25. data/refile.gemspec +34 -0
  26. data/spec/refile/app_spec.rb +151 -0
  27. data/spec/refile/attachment_spec.rb +141 -0
  28. data/spec/refile/backend/file_system_spec.rb +30 -0
  29. data/spec/refile/backend/s3_spec.rb +11 -0
  30. data/spec/refile/backend_examples.rb +215 -0
  31. data/spec/refile/features/direct_upload_spec.rb +29 -0
  32. data/spec/refile/features/normal_upload_spec.rb +36 -0
  33. data/spec/refile/features/presigned_upload_spec.rb +29 -0
  34. data/spec/refile/fixtures/hello.txt +1 -0
  35. data/spec/refile/fixtures/large.txt +44 -0
  36. data/spec/refile/spec_helper.rb +58 -0
  37. data/spec/refile/test_app.rb +46 -0
  38. data/spec/refile/test_app/app/assets/javascripts/application.js +40 -0
  39. data/spec/refile/test_app/app/controllers/application_controller.rb +2 -0
  40. data/spec/refile/test_app/app/controllers/direct_posts_controller.rb +15 -0
  41. data/spec/refile/test_app/app/controllers/home_controller.rb +4 -0
  42. data/spec/refile/test_app/app/controllers/normal_posts_controller.rb +19 -0
  43. data/spec/refile/test_app/app/controllers/presigned_posts_controller.rb +30 -0
  44. data/spec/refile/test_app/app/models/post.rb +5 -0
  45. data/spec/refile/test_app/app/views/direct_posts/new.html.erb +16 -0
  46. data/spec/refile/test_app/app/views/home/index.html.erb +1 -0
  47. data/spec/refile/test_app/app/views/layouts/application.html.erb +14 -0
  48. data/spec/refile/test_app/app/views/normal_posts/new.html.erb +20 -0
  49. data/spec/refile/test_app/app/views/normal_posts/show.html.erb +9 -0
  50. data/spec/refile/test_app/app/views/presigned_posts/new.html.erb +16 -0
  51. data/spec/refile/test_app/config/database.yml +7 -0
  52. data/spec/refile/test_app/config/routes.rb +17 -0
  53. data/spec/refile/test_app/public/favicon.ico +0 -0
  54. data/spec/refile_spec.rb +35 -0
  55. 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,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -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,4 @@
1
+ class HomeController < ApplicationController
2
+ def index
3
+ end
4
+ 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,5 @@
1
+ class Post < ActiveRecord::Base
2
+ attachment :image
3
+ attachment :document, cache: :limited_cache
4
+ validates_presence_of :title
5
+ 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,14 @@
1
+ <!doctype html>
2
+
3
+ <html>
4
+ <head>
5
+ <%= javascript_include_tag "application" %>
6
+
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Refile Test App</h1>
11
+
12
+ <%= yield %>
13
+ </body>
14
+ </html>
@@ -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,9 @@
1
+ <h1><%= @post.title %></h1<>
2
+
3
+ <% if @post.image %>
4
+ <%= attachment_image_tag(@post, :image) %>
5
+ <% end %>
6
+
7
+ <% if @post.document %>
8
+ <%= link_to "Document", attachment_url(@post, :document) %>
9
+ <% 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,7 @@
1
+ development: &default
2
+ adapter: "sqlite3"
3
+ database: "db/db.sqlite"
4
+ verbosity: "quiet"
5
+
6
+ test:
7
+ <<: *default
@@ -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
@@ -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