From dd50144bcd4dbd605995123ab5afc99e40e9a630 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 30 Jun 2017 19:12:58 +0200 Subject: [PATCH 001/289] First sketching --- Gemfile | 6 +++++ Gemfile.lock | 40 ++++++++++++++++++++++++++++++++ MIT-LICENSE | 20 ++++++++++++++++ README.md | 45 ++++++++++++++++++++++++++++++++++++ Rakefile | 10 ++++++++ activefile.gemspec | 20 ++++++++++++++++ lib/active_file.rb | 8 +++++++ lib/active_file/blob.rb | 45 ++++++++++++++++++++++++++++++++++++ lib/active_file/filename.rb | 31 +++++++++++++++++++++++++ lib/active_file/migration.rb | 14 +++++++++++ lib/active_file/purge_job.rb | 7 ++++++ lib/active_file/railtie.rb | 6 +++++ lib/active_file/store.rb | 26 +++++++++++++++++++++ test/blob_test.rb | 7 ++++++ test/test_helper.rb | 4 ++++ 15 files changed, 289 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 MIT-LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 activefile.gemspec create mode 100644 lib/active_file.rb create mode 100644 lib/active_file/blob.rb create mode 100644 lib/active_file/filename.rb create mode 100644 lib/active_file/migration.rb create mode 100644 lib/active_file/purge_job.rb create mode 100644 lib/active_file/railtie.rb create mode 100644 lib/active_file/store.rb create mode 100644 test/blob_test.rb create mode 100644 test/test_helper.rb diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..dbddb9f913 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gemspec + +gem 'rake' +gem 'byebug' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..0d106cecfc --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,40 @@ +PATH + remote: . + specs: + google_sign_in (0.1) + activesupport (>= 5.1) + google-id-token (>= 1.3.1) + +GEM + remote: https://rubygems.org/ + specs: + activesupport (5.1.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + byebug (9.0.6) + concurrent-ruby (1.0.5) + google-id-token (1.3.1) + jwt + multi_json + i18n (0.8.1) + jwt (1.5.6) + minitest (5.10.2) + multi_json (1.12.1) + rake (12.0.0) + thread_safe (0.3.6) + tzinfo (1.2.3) + thread_safe (~> 0.1) + +PLATFORMS + ruby + +DEPENDENCIES + bundler (~> 1.15) + byebug + google_sign_in! + rake + +BUNDLED WITH + 1.15.0 diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000000..4e1c6cad79 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017 David Heinemeier Hansson, Basecamp + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..fccaa2d2bb --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Active File + +... + +## Example + +class Person < ApplicationRecord + has_one :avatar +end + +class Avatar < ApplicationRecord + belongs_to :person + belongs_to :image, class_name: 'ActiveFile::Blob' + + has_file :image +end + +avatar.image.url(expires_in: 5.minutes) + + +class ActiveFile::DownloadsController < ActionController::Base + def show + head :ok, ActiveFile::Blob.locate(params[:id]).download_headers + end +end + + +class AvatarsController < ApplicationController + def create + # @avatar = Avatar.create \ + # image: ActiveFile::Blob.save!(file_name: params.require(:name), content_type: request.content_type, data: request.body) + @avatar = Avatar.create! image: Avatar.image.extract_from(request) + end +end + + +class ProfilesController < ApplicationController + def update + @person.update! avatar: @person.avatar.update!(image: ) + end +end + +## License + +Google Sign-In for Rails is released under the [MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000..a61ad18bca --- /dev/null +++ b/Rakefile @@ -0,0 +1,10 @@ +require "bundler/setup" +require "bundler/gem_tasks" +require "rake/testtask" + +Rake::TestTask.new do |test| + test.libs << "test" + test.test_files = FileList["test/*_test.rb"] +end + +task default: :test diff --git a/activefile.gemspec b/activefile.gemspec new file mode 100644 index 0000000000..20deecff23 --- /dev/null +++ b/activefile.gemspec @@ -0,0 +1,20 @@ +Gem::Specification.new do |s| + s.name = 'activefile' + s.version = '0.1' + s.authors = 'David Heinemeier Hansson' + s.email = 'david@basecamp.com' + s.summary = 'Store files in Rails applications' + s.homepage = 'https://github.com/rails/activefile' + s.license = 'MIT' + + s.required_ruby_version = '>= 1.9.3' + + s.add_dependency 'activesupport', '>= 5.1' + s.add_dependency 'activerecord', '>= 5.1' + s.add_dependency 'activejob', '>= 5.1' + + s.add_development_dependency 'bundler', '~> 1.15' + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- test/*`.split("\n") +end diff --git a/lib/active_file.rb b/lib/active_file.rb new file mode 100644 index 0000000000..7dbcf95163 --- /dev/null +++ b/lib/active_file.rb @@ -0,0 +1,8 @@ +require "active_record" +require "active_file/railtie" if defined?(Rails) + +module ActiveFile + extend ActiveSupport::Autoload + + autoload :Blob +end \ No newline at end of file diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb new file mode 100644 index 0000000000..248b136903 --- /dev/null +++ b/lib/active_file/blob.rb @@ -0,0 +1,45 @@ +# Schema: id, token, filename, content_type, metadata, byte_size, digest, created_at +class ActiveFile::Blob < ActiveRecord::Base + self.table_name = "rails_active_file_blobs" + + store :metadata, coder: JSON + has_secure_token + + class_attribute :verifier, default: -> { Rails.application.message_verifier('ActiveFile') } + class_attribute :storage + + class << self + def find_verified(signed_id) + find(verifier.verify(signed_id)) + end + + def build_after_upload(data:, filename:, content_type: nil, metadata: nil) + new.tap do |blob| + blob.filename = name + blob.content_type = Marcel::MimeType.for(data, name: name, declared_type: content_type) + blob.data = data + end + end + + def create_after_upload!(data:, filename:, content_type: nil, metadata: nil) + build_after_upload(data: data, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!) + end + end + + def filename + Filename.new(filename) + end + + def delete + storage.delete token + end + + def purge + delete + destroy + end + + def purge_later + ActiveFile::PurgeJob.perform_later(self) + end +end diff --git a/lib/active_file/filename.rb b/lib/active_file/filename.rb new file mode 100644 index 0000000000..b3c184e26c --- /dev/null +++ b/lib/active_file/filename.rb @@ -0,0 +1,31 @@ +class ActiveFile::Filename + include Comparable + + def initialize(filename) + @filename = filename + end + + def extname + File.extname(@filename) + end + + def extension + extname.from(1) + end + + def base + File.basename(@filename, extname) + end + + def sanitized + @filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-") + end + + def to_s + sanitized.to_s + end + + def <=>(other) + to_s.downcase <=> other.to_s.downcase + end +end diff --git a/lib/active_file/migration.rb b/lib/active_file/migration.rb new file mode 100644 index 0000000000..0f6b0a3fd2 --- /dev/null +++ b/lib/active_file/migration.rb @@ -0,0 +1,14 @@ +class ActiveFile::CreateBlobs < ActiveRecord::Migration[5.2] + def change + create_table :rails_active_file_blobs do |t| + t.string :token + t.string :filename + t.string :content_type + t.integer :byte_size + t.string :digest + t.time :created_at + + t.index [ :token ], unique: true + end + end +end diff --git a/lib/active_file/purge_job.rb b/lib/active_file/purge_job.rb new file mode 100644 index 0000000000..1a967db2f0 --- /dev/null +++ b/lib/active_file/purge_job.rb @@ -0,0 +1,7 @@ +class ActiveFile::PurgeJob < ActiveJob::Base + retry_on ActiveFile::StorageException + + def perform(blob) + blob.purge + end +end diff --git a/lib/active_file/railtie.rb b/lib/active_file/railtie.rb new file mode 100644 index 0000000000..ccba844742 --- /dev/null +++ b/lib/active_file/railtie.rb @@ -0,0 +1,6 @@ +require 'rails/railtie' + +module ActiveFile + class Engine < ::Rails::Engine + end +end diff --git a/lib/active_file/store.rb b/lib/active_file/store.rb new file mode 100644 index 0000000000..bdac4eab9e --- /dev/null +++ b/lib/active_file/store.rb @@ -0,0 +1,26 @@ +class ActiveFile::Store + def upload(key, data) + end + + def download(key) + end + + def delete(key) + end + + def exists?(key) + end + + def url(key) + end + + def checksum(key) + end + + + def copy(from_key:, to_key:) + end + + def move(from_key:, to_key:) + end +end diff --git a/test/blob_test.rb b/test/blob_test.rb new file mode 100644 index 0000000000..3ebde08b90 --- /dev/null +++ b/test/blob_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class ActiveFile::BlobTest < ActiveSupport::TestCase + test "truth" do + assert true + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000000..c05ba0c70c --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,4 @@ +require 'bundler/setup' +require 'active_support' +require 'active_support/testing/autorun' +require 'byebug' From 2ea3ef9f775dc9432efa25ebe240c41cf74da1c0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 00:02:30 +0200 Subject: [PATCH 002/289] Use key instead of token More familiar in this context --- lib/active_file/blob.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 248b136903..9df2119a89 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -1,9 +1,9 @@ -# Schema: id, token, filename, content_type, metadata, byte_size, digest, created_at +# Schema: id, key, filename, content_type, metadata, byte_size, digest, created_at class ActiveFile::Blob < ActiveRecord::Base self.table_name = "rails_active_file_blobs" + has_secure_token :key store :metadata, coder: JSON - has_secure_token class_attribute :verifier, default: -> { Rails.application.message_verifier('ActiveFile') } class_attribute :storage From f3fa8f4b0603fe5ddbf7a6d6ea9692bdb9ffe380 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 00:02:50 +0200 Subject: [PATCH 003/289] No need for rails prefix active_file is specific enough. --- lib/active_file/blob.rb | 2 +- lib/active_file/migration.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 9df2119a89..201edf24df 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -1,6 +1,6 @@ # Schema: id, key, filename, content_type, metadata, byte_size, digest, created_at class ActiveFile::Blob < ActiveRecord::Base - self.table_name = "rails_active_file_blobs" + self.table_name = "active_file_blobs" has_secure_token :key store :metadata, coder: JSON diff --git a/lib/active_file/migration.rb b/lib/active_file/migration.rb index 0f6b0a3fd2..6e5ed0c997 100644 --- a/lib/active_file/migration.rb +++ b/lib/active_file/migration.rb @@ -1,6 +1,6 @@ class ActiveFile::CreateBlobs < ActiveRecord::Migration[5.2] def change - create_table :rails_active_file_blobs do |t| + create_table :active_file_blobs do |t| t.string :token t.string :filename t.string :content_type From d30231e983c019fbe3fcd6a2a06a461fefa58dab Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 00:04:19 +0200 Subject: [PATCH 004/289] Go with site instead of store Better fit for upload/download terminology. --- lib/active_file/blob.rb | 6 ++- lib/active_file/{store.rb => site.rb} | 12 +++++- lib/active_file/sites/disk_site.rb | 53 +++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) rename lib/active_file/{store.rb => site.rb} (53%) create mode 100644 lib/active_file/sites/disk_site.rb diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 201edf24df..d3bc831926 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -1,3 +1,5 @@ +require "active_file/site" + # Schema: id, key, filename, content_type, metadata, byte_size, digest, created_at class ActiveFile::Blob < ActiveRecord::Base self.table_name = "active_file_blobs" @@ -6,7 +8,7 @@ class ActiveFile::Blob < ActiveRecord::Base store :metadata, coder: JSON class_attribute :verifier, default: -> { Rails.application.message_verifier('ActiveFile') } - class_attribute :storage + class_attribute :site class << self def find_verified(signed_id) @@ -31,7 +33,7 @@ def filename end def delete - storage.delete token + site.delete token end def purge diff --git a/lib/active_file/store.rb b/lib/active_file/site.rb similarity index 53% rename from lib/active_file/store.rb rename to lib/active_file/site.rb index bdac4eab9e..8a915c2e17 100644 --- a/lib/active_file/store.rb +++ b/lib/active_file/site.rb @@ -1,4 +1,7 @@ -class ActiveFile::Store +class ActiveFile::Site + def initialize + end + def upload(key, data) end @@ -23,4 +26,11 @@ def copy(from_key:, to_key:) def move(from_key:, to_key:) end + + + private + def normalize_key(key) + # disallow "." and ".." segments in the key + key.split(%r[/]).reject { |s| s == "." || s == ".." } + end end diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb new file mode 100644 index 0000000000..71dc8d078b --- /dev/null +++ b/lib/active_file/sites/disk_site.rb @@ -0,0 +1,53 @@ +class ActiveFile::Sites::DiskSite < ActiveFile::Site + attr_reader :root + + def initialize(root) + @root = root + end + + def upload(key, data) + File.open(make_path_for(key), "wb") do |file| + while chunk = data.read(65536) + file.write(chunk) + end + end + end + + def download(key) + if block_given? + open(key) do |file| + while data = file.read(65536) + yield data + end + end + else + open(key, &:read) + end + end + + def delete(key) + File.delete(path_for(key)) + true + end + + def size(key) + File.size(path_for(key)) + end + + def checksum(key) + Digest::MD5.file(path_for(key)).hexdigest + end + + private + def path_for(key) + File.join(root, folder_for(key), normalize(key)) + end + + def folder_for(key) + [key[0..1], key[2..3]].join("/") + end + + def make_path_for(key) + path_for(key).tap { |path| FileUtils.mkdir_p File.dirname(path) } + end +end From d9adfa881774c31fe10e6ae76840d6abd539f9db Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 00:05:04 +0200 Subject: [PATCH 005/289] Its a key now --- lib/active_file/blob.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index d3bc831926..4e73ec4864 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -33,7 +33,7 @@ def filename end def delete - site.delete token + site.delete(key) end def purge From 3959d32aa09206518f4c1ea2cd96fa62be383dcd Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 00:13:23 +0200 Subject: [PATCH 006/289] Space to breathe --- lib/active_file/blob.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 4e73ec4864..817617ecaf 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -28,10 +28,12 @@ def create_after_upload!(data:, filename:, content_type: nil, metadata: nil) end end + def filename Filename.new(filename) end + def delete site.delete(key) end From ce44746ea3a98fab1af65c75ec32eeadd67b1b5f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 00:13:50 +0200 Subject: [PATCH 007/289] No need to normalize since we generate our own keys --- lib/active_file/site.rb | 6 ------ lib/active_file/sites/disk_site.rb | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 8a915c2e17..7a3b1f14ba 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -27,10 +27,4 @@ def copy(from_key:, to_key:) def move(from_key:, to_key:) end - - private - def normalize_key(key) - # disallow "." and ".." segments in the key - key.split(%r[/]).reject { |s| s == "." || s == ".." } - end end diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index 71dc8d078b..da9c44a612 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -40,7 +40,7 @@ def checksum(key) private def path_for(key) - File.join(root, folder_for(key), normalize(key)) + File.join(root, folder_for(key), key) end def folder_for(key) From e8346e7522dd046eedab162475522c1fef685957 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 00:14:04 +0200 Subject: [PATCH 008/289] Don't give return guarentees --- lib/active_file/sites/disk_site.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index da9c44a612..0118e8b6d4 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -27,7 +27,6 @@ def download(key) def delete(key) File.delete(path_for(key)) - true end def size(key) From a2ac7af389e065946e3b26ecc951c77997eb06b3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 00:14:13 +0200 Subject: [PATCH 009/289] Right paths --- lib/active_file/sites/disk_site.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index 0118e8b6d4..eab48978f2 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -15,13 +15,13 @@ def upload(key, data) def download(key) if block_given? - open(key) do |file| + open(path_for(key)) do |file| while data = file.read(65536) yield data end end else - open(key, &:read) + open(path_for(key), &:read) end end From c39e176eb5b491d3f218f61fc7a7c67d56ab270c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 00:14:22 +0200 Subject: [PATCH 010/289] Require what we need --- lib/active_file/site.rb | 4 +++- lib/active_file/sites/disk_site.rb | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 7a3b1f14ba..7cd33e11cc 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -26,5 +26,7 @@ def copy(from_key:, to_key:) def move(from_key:, to_key:) end - +end + +module ActiveFile::Sites end diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index eab48978f2..2466b665c4 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -1,3 +1,8 @@ +require "active_file/site" + +require "fileutils" +require "pathname" + class ActiveFile::Sites::DiskSite < ActiveFile::Site attr_reader :root From 0e9ecc2a674d03b2953e90052ca6b4f1f5209d5b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:05:32 +0200 Subject: [PATCH 011/289] Switch to double quotes for Rails linter --- activefile.gemspec | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/activefile.gemspec b/activefile.gemspec index 20deecff23..e05421102d 100644 --- a/activefile.gemspec +++ b/activefile.gemspec @@ -1,19 +1,19 @@ Gem::Specification.new do |s| - s.name = 'activefile' - s.version = '0.1' - s.authors = 'David Heinemeier Hansson' - s.email = 'david@basecamp.com' - s.summary = 'Store files in Rails applications' - s.homepage = 'https://github.com/rails/activefile' - s.license = 'MIT' + s.name = "activefile" + s.version = "0.1" + s.authors = "David Heinemeier Hansson" + s.email = "david@basecamp.com" + s.summary = "Store files in Rails applications" + s.homepage = "https://github.com/rails/activefile" + s.license = "MIT" - s.required_ruby_version = '>= 1.9.3' + s.required_ruby_version = ">= 1.9.3" - s.add_dependency 'activesupport', '>= 5.1' - s.add_dependency 'activerecord', '>= 5.1' - s.add_dependency 'activejob', '>= 5.1' + s.add_dependency "activesupport", ">= 5.1" + s.add_dependency "activerecord", ">= 5.1" + s.add_dependency "activejob", ">= 5.1" - s.add_development_dependency 'bundler', '~> 1.15' + s.add_development_dependency "bundler", "~> 1.15" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- test/*`.split("\n") From 97b2978d70757fe538898fdf21af359bc46fd5c5 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:05:42 +0200 Subject: [PATCH 012/289] Actual dependencies --- Gemfile.lock | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0d106cecfc..c990ec9a72 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,27 +1,35 @@ PATH remote: . specs: - google_sign_in (0.1) + activefile (0.1) + activejob (>= 5.1) + activerecord (>= 5.1) activesupport (>= 5.1) - google-id-token (>= 1.3.1) GEM remote: https://rubygems.org/ specs: + activejob (5.1.1) + activesupport (= 5.1.1) + globalid (>= 0.3.6) + activemodel (5.1.1) + activesupport (= 5.1.1) + activerecord (5.1.1) + activemodel (= 5.1.1) + activesupport (= 5.1.1) + arel (~> 8.0) activesupport (5.1.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) + arel (8.0.0) byebug (9.0.6) concurrent-ruby (1.0.5) - google-id-token (1.3.1) - jwt - multi_json - i18n (0.8.1) - jwt (1.5.6) + globalid (0.4.0) + activesupport (>= 4.2.0) + i18n (0.8.4) minitest (5.10.2) - multi_json (1.12.1) rake (12.0.0) thread_safe (0.3.6) tzinfo (1.2.3) @@ -31,10 +39,10 @@ PLATFORMS ruby DEPENDENCIES + activefile! bundler (~> 1.15) byebug - google_sign_in! rake BUNDLED WITH - 1.15.0 + 1.15.1 From fcdbaf7e614dc503eca93227b45c54fcbf091253 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:05:58 +0200 Subject: [PATCH 013/289] Style --- lib/active_file/sites/disk_site.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index 2466b665c4..9068e14866 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -20,22 +20,26 @@ def upload(key, data) def download(key) if block_given? - open(path_for(key)) do |file| + File.open(path_for(key)) do |file| while data = file.read(65536) yield data end end else - open(path_for(key), &:read) + File.open path_for(key), &:read end end def delete(key) - File.delete(path_for(key)) + File.delete path_for(key) + end + + def exists?(key) + File.exist? path_for(key) end def size(key) - File.size(path_for(key)) + File.size path_for(key) end def checksum(key) @@ -44,11 +48,11 @@ def checksum(key) private def path_for(key) - File.join(root, folder_for(key), key) + File.join root, folder_for(key), key end def folder_for(key) - [key[0..1], key[2..3]].join("/") + [ key[0..1], key[2..3] ].join("/") end def make_path_for(key) From e50454e077d6253274fb24b5a027365bd32c45b7 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:06:08 +0200 Subject: [PATCH 014/289] Quote this! --- lib/active_file/railtie.rb | 2 +- test/blob_test.rb | 2 +- test/test_helper.rb | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/active_file/railtie.rb b/lib/active_file/railtie.rb index ccba844742..e1b34f56cf 100644 --- a/lib/active_file/railtie.rb +++ b/lib/active_file/railtie.rb @@ -1,4 +1,4 @@ -require 'rails/railtie' +require "rails/railtie" module ActiveFile class Engine < ::Rails::Engine diff --git a/test/blob_test.rb b/test/blob_test.rb index 3ebde08b90..9f7c14533e 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require "test_helper" class ActiveFile::BlobTest < ActiveSupport::TestCase test "truth" do diff --git a/test/test_helper.rb b/test/test_helper.rb index c05ba0c70c..0964774e00 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,6 @@ -require 'bundler/setup' -require 'active_support' -require 'active_support/testing/autorun' -require 'byebug' +require "bundler/setup" +require "active_support" +require "active_support/testing/autorun" +require "byebug" + +require "active_file" From e47ef8a71c0bd37b7e37392f0b68c7070ca6cdb3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:06:16 +0200 Subject: [PATCH 015/289] Test DiskSite --- test/disk_site_test.rb | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/disk_site_test.rb diff --git a/test/disk_site_test.rb b/test/disk_site_test.rb new file mode 100644 index 0000000000..b291ae74b7 --- /dev/null +++ b/test/disk_site_test.rb @@ -0,0 +1,39 @@ +require "test_helper" +require "fileutils" +require "tmpdir" +require "active_support/core_ext/securerandom" +require "active_file/sites/disk_site" + +class ActiveFile::DiskSiteTest < ActiveSupport::TestCase + FIXTURE_KEY = SecureRandom.base58(24) + FIXTURE_FILE = StringIO.new("Hello world!") + + setup do + @site = ActiveFile::Sites::DiskSite.new(File.join(Dir.tmpdir, "active_file")) + @site.upload FIXTURE_KEY, FIXTURE_FILE + FIXTURE_FILE.rewind + end + + teardown do + FileUtils.rm_rf @site.root + FIXTURE_FILE.rewind + end + + test "downloading" do + assert_equal FIXTURE_FILE.read, @site.download(FIXTURE_KEY) + end + + test "existing" do + assert @site.exists?(FIXTURE_KEY) + assert_not @site.exists?(FIXTURE_KEY + "nonsense") + end + + test "deleting" do + @site.delete FIXTURE_KEY + assert_not @site.exists?(FIXTURE_KEY) + end + + test "sizing" do + assert_equal FIXTURE_FILE.size, @site.size(FIXTURE_KEY) + end +end From 97fe304af2f55791e08b8dfff7e5ae93f7e7a5b8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:09:54 +0200 Subject: [PATCH 016/289] Ignore byebug history --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..36298d2843 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.byebug_history From 4038bda96bba66f64f8f1322e031eefc65616c58 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:10:11 +0200 Subject: [PATCH 017/289] Underscore its an interface --- lib/active_file/site.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 7cd33e11cc..d7035a3f09 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -1,30 +1,39 @@ +# Abstract class serving as an interface for concrete sites. class ActiveFile::Site def initialize end def upload(key, data) + raise NotImplementedError end def download(key) + raise NotImplementedError end def delete(key) + raise NotImplementedError end def exists?(key) + raise NotImplementedError end def url(key) + raise NotImplementedError end def checksum(key) + raise NotImplementedError end def copy(from_key:, to_key:) + raise NotImplementedError end def move(from_key:, to_key:) + raise NotImplementedError end end From ea429eaa14cfb04f931053256624a8e7c820ca33 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:10:22 +0200 Subject: [PATCH 018/289] Implied well enough --- lib/active_file/site.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index d7035a3f09..2a9043567c 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -28,11 +28,11 @@ def checksum(key) end - def copy(from_key:, to_key:) + def copy(from:, to:) raise NotImplementedError end - def move(from_key:, to_key:) + def move(from:, to:) raise NotImplementedError end end From 879f0c5caf0eacf04bc931e44c41ecd2edb8bbc1 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:10:28 +0200 Subject: [PATCH 019/289] Autoload site --- lib/active_file.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/active_file.rb b/lib/active_file.rb index 7dbcf95163..6633ba9e82 100644 --- a/lib/active_file.rb +++ b/lib/active_file.rb @@ -5,4 +5,5 @@ module ActiveFile extend ActiveSupport::Autoload autoload :Blob + autoload :Site end \ No newline at end of file From 8ec90d0b934d30c2e39626e0436fba13ad96f695 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:11:04 +0200 Subject: [PATCH 020/289] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fccaa2d2bb..737e47041f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ ## Example +```ruby class Person < ApplicationRecord has_one :avatar end @@ -39,6 +40,7 @@ class ProfilesController < ApplicationController @person.update! avatar: @person.avatar.update!(image: ) end end +``` ## License From a239abb7fcaea4c271ea4bef031eda158d7cf8ad Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:28:47 +0200 Subject: [PATCH 021/289] Test checksumming --- test/disk_site_test.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/disk_site_test.rb b/test/disk_site_test.rb index b291ae74b7..0c5de70c72 100644 --- a/test/disk_site_test.rb +++ b/test/disk_site_test.rb @@ -36,4 +36,8 @@ class ActiveFile::DiskSiteTest < ActiveSupport::TestCase test "sizing" do assert_equal FIXTURE_FILE.size, @site.size(FIXTURE_KEY) end + + test "checksumming" do + assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @site.checksum(FIXTURE_KEY) + end end From 182445e1b4b2e12542457ba32f255a0cc2f01910 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:47:13 +0200 Subject: [PATCH 022/289] Test blobs with real db backend --- Gemfile | 1 + Gemfile.lock | 2 ++ lib/active_file/blob.rb | 19 ++++++++++++++++--- lib/active_file/migration.rb | 4 ++-- test/blob_test.rb | 9 +++++++-- test/database/setup.rb | 4 ++++ 6 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 test/database/setup.rb diff --git a/Gemfile b/Gemfile index dbddb9f913..5d3b906243 100644 --- a/Gemfile +++ b/Gemfile @@ -4,3 +4,4 @@ gemspec gem 'rake' gem 'byebug' +gem 'sqlite3' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index c990ec9a72..d7dc3e105d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -31,6 +31,7 @@ GEM i18n (0.8.4) minitest (5.10.2) rake (12.0.0) + sqlite3 (1.3.13) thread_safe (0.3.6) tzinfo (1.2.3) thread_safe (~> 0.1) @@ -43,6 +44,7 @@ DEPENDENCIES bundler (~> 1.15) byebug rake + sqlite3 BUNDLED WITH 1.15.1 diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 817617ecaf..75e606b68b 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -18,8 +18,8 @@ def find_verified(signed_id) def build_after_upload(data:, filename:, content_type: nil, metadata: nil) new.tap do |blob| blob.filename = name - blob.content_type = Marcel::MimeType.for(data, name: name, declared_type: content_type) - blob.data = data + blob.content_type = content_type # Marcel::MimeType.for(data, name: name, declared_type: content_type) + blob.upload data end end @@ -28,14 +28,27 @@ def create_after_upload!(data:, filename:, content_type: nil, metadata: nil) end end + # We can't wait until the record is first saved to have a key for it + def key + self[:key] ||= self.class.generate_unique_secure_token + end def filename Filename.new(filename) end + def upload(data) + site.upload key, data + end + + def download + site.download key + end + + def delete - site.delete(key) + site.delete key end def purge diff --git a/lib/active_file/migration.rb b/lib/active_file/migration.rb index 6e5ed0c997..7a424722e0 100644 --- a/lib/active_file/migration.rb +++ b/lib/active_file/migration.rb @@ -1,7 +1,7 @@ -class ActiveFile::CreateBlobs < ActiveRecord::Migration[5.2] +class ActiveFile::CreateBlobs < ActiveRecord::Migration[5.1] def change create_table :active_file_blobs do |t| - t.string :token + t.string :key t.string :filename t.string :content_type t.integer :byte_size diff --git a/test/blob_test.rb b/test/blob_test.rb index 9f7c14533e..ad2df51ca9 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -1,7 +1,12 @@ require "test_helper" +require "database/setup" +require "active_file/blob" + +ActiveFile::Blob.site = ActiveFile::Sites::DiskSite.new(File.join(Dir.tmpdir, "active_file")) class ActiveFile::BlobTest < ActiveSupport::TestCase - test "truth" do - assert true + test "create after upload" do + blob = ActiveFile::Blob.create_after_upload! data: StringIO.new("Hello world!"), filename: "hello.txt", content_type: "text/plain" + assert_equal "Hello world!", blob.download end end diff --git a/test/database/setup.rb b/test/database/setup.rb new file mode 100644 index 0000000000..21ede8f49c --- /dev/null +++ b/test/database/setup.rb @@ -0,0 +1,4 @@ +require "active_file/migration" + +ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') +ActiveFile::CreateBlobs.migrate(:up) From 27c2516f4868863ea27b85f94885641f61add700 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 12:47:25 +0200 Subject: [PATCH 023/289] Sort out circular dependency for now --- lib/active_file/site.rb | 2 ++ lib/active_file/sites/disk_site.rb | 2 -- test/disk_site_test.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 2a9043567c..44010767dd 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -39,3 +39,5 @@ def move(from:, to:) module ActiveFile::Sites end + +require "active_file/sites/disk_site" diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index 9068e14866..2f3871c65f 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -1,5 +1,3 @@ -require "active_file/site" - require "fileutils" require "pathname" diff --git a/test/disk_site_test.rb b/test/disk_site_test.rb index 0c5de70c72..076cbd8a3f 100644 --- a/test/disk_site_test.rb +++ b/test/disk_site_test.rb @@ -2,7 +2,7 @@ require "fileutils" require "tmpdir" require "active_support/core_ext/securerandom" -require "active_file/sites/disk_site" +require "active_file/site" class ActiveFile::DiskSiteTest < ActiveSupport::TestCase FIXTURE_KEY = SecureRandom.base58(24) From 2571d1a8491169ab3ef78eec5b891f782a533496 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 14:24:00 +0200 Subject: [PATCH 024/289] Match domain language --- lib/active_file/blob.rb | 2 +- lib/active_file/migration.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 75e606b68b..5ed2fce5ac 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -1,6 +1,6 @@ require "active_file/site" -# Schema: id, key, filename, content_type, metadata, byte_size, digest, created_at +# Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at class ActiveFile::Blob < ActiveRecord::Base self.table_name = "active_file_blobs" diff --git a/lib/active_file/migration.rb b/lib/active_file/migration.rb index 7a424722e0..a6f398106e 100644 --- a/lib/active_file/migration.rb +++ b/lib/active_file/migration.rb @@ -5,7 +5,7 @@ def change t.string :filename t.string :content_type t.integer :byte_size - t.string :digest + t.string :checksum t.time :created_at t.index [ :token ], unique: true From 8e4e9741a8280790d63ee8c559ebfbc5c92ea52c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 14:24:25 +0200 Subject: [PATCH 025/289] Standardize on #byte_size --- lib/active_file/sites/disk_site.rb | 3 ++- test/disk_site_test.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index 2f3871c65f..2bf80d07f4 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -36,7 +36,8 @@ def exists?(key) File.exist? path_for(key) end - def size(key) + + def byte_size(key) File.size path_for(key) end diff --git a/test/disk_site_test.rb b/test/disk_site_test.rb index 076cbd8a3f..7713189e0a 100644 --- a/test/disk_site_test.rb +++ b/test/disk_site_test.rb @@ -34,7 +34,7 @@ class ActiveFile::DiskSiteTest < ActiveSupport::TestCase end test "sizing" do - assert_equal FIXTURE_FILE.size, @site.size(FIXTURE_KEY) + assert_equal FIXTURE_FILE.size, @site.byte_size(FIXTURE_KEY) end test "checksumming" do From 1e05e6285602656bc3504b83d03135c343cd50e6 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 14:24:43 +0200 Subject: [PATCH 026/289] Test basic upload --- test/disk_site_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/disk_site_test.rb b/test/disk_site_test.rb index 7713189e0a..afae5d4683 100644 --- a/test/disk_site_test.rb +++ b/test/disk_site_test.rb @@ -19,6 +19,14 @@ class ActiveFile::DiskSiteTest < ActiveSupport::TestCase FIXTURE_FILE.rewind end + test "uploading" do + key = SecureRandom.base58(24) + data = "Something else entirely!" + @site.upload(key, StringIO.new(data)) + + assert_equal data, @site.download(key) + end + test "downloading" do assert_equal FIXTURE_FILE.read, @site.download(FIXTURE_KEY) end From a9d2ce5d18288223fce5a8095c2fb37520017828 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 14:24:50 +0200 Subject: [PATCH 027/289] Breathing room --- lib/active_file/sites/disk_site.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index 2bf80d07f4..d61b6c3c5d 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -8,6 +8,7 @@ def initialize(root) @root = root end + def upload(key, data) File.open(make_path_for(key), "wb") do |file| while chunk = data.read(65536) @@ -45,6 +46,7 @@ def checksum(key) Digest::MD5.file(path_for(key)).hexdigest end + private def path_for(key) File.join root, folder_for(key), key From 59d3e03b81d09c70fc1249514c1848e701757513 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 1 Jul 2017 14:25:02 +0200 Subject: [PATCH 028/289] Uploading will set blob's byte size and checksum --- lib/active_file/blob.rb | 5 ++++- test/blob_test.rb | 10 +++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 5ed2fce5ac..b5d149e1fa 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -39,7 +39,10 @@ def filename def upload(data) - site.upload key, data + site.upload(key, data) + + self.checksum = site.checksum(key) + self.byte_size = site.byte_size(key) end def download diff --git a/test/blob_test.rb b/test/blob_test.rb index ad2df51ca9..04d636e189 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -5,8 +5,12 @@ ActiveFile::Blob.site = ActiveFile::Sites::DiskSite.new(File.join(Dir.tmpdir, "active_file")) class ActiveFile::BlobTest < ActiveSupport::TestCase - test "create after upload" do - blob = ActiveFile::Blob.create_after_upload! data: StringIO.new("Hello world!"), filename: "hello.txt", content_type: "text/plain" - assert_equal "Hello world!", blob.download + test "create after upload sets byte size and checksum" do + data = "Hello world!" + blob = ActiveFile::Blob.create_after_upload! data: StringIO.new(data), filename: "hello.txt", content_type: "text/plain" + + assert_equal data, blob.download + assert_equal data.length, blob.byte_size + assert_equal Digest::MD5.hexdigest(data), blob.checksum end end From cc2c5f428ae0606fe37050772c248bafafd187f0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 2 Jul 2017 16:47:28 +0200 Subject: [PATCH 029/289] Start on S3 site --- Gemfile | 4 +- Gemfile.lock | 10 +++++ lib/active_file/sites/s3_site.rb | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 lib/active_file/sites/s3_site.rb diff --git a/Gemfile b/Gemfile index 5d3b906243..d502c9387e 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,6 @@ gemspec gem 'rake' gem 'byebug' -gem 'sqlite3' \ No newline at end of file + +gem 'sqlite3' +gem 'aws-sdk' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index d7dc3e105d..0438cb67fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,11 +24,20 @@ GEM minitest (~> 5.1) tzinfo (~> 1.1) arel (8.0.0) + aws-sdk (2.10.7) + aws-sdk-resources (= 2.10.7) + aws-sdk-core (2.10.7) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-resources (2.10.7) + aws-sdk-core (= 2.10.7) + aws-sigv4 (1.0.0) byebug (9.0.6) concurrent-ruby (1.0.5) globalid (0.4.0) activesupport (>= 4.2.0) i18n (0.8.4) + jmespath (1.3.1) minitest (5.10.2) rake (12.0.0) sqlite3 (1.3.13) @@ -41,6 +50,7 @@ PLATFORMS DEPENDENCIES activefile! + aws-sdk bundler (~> 1.15) byebug rake diff --git a/lib/active_file/sites/s3_site.rb b/lib/active_file/sites/s3_site.rb new file mode 100644 index 0000000000..46c409405a --- /dev/null +++ b/lib/active_file/sites/s3_site.rb @@ -0,0 +1,69 @@ +require "aws-sdk" + +class ActiveFile::Sites::S3Site < ActiveFile::Site + attr_reader :client, :bucket + + def initialize(access_key_id:, secret_access_key:, region:, bucket:) + @client = Aws::S3::Resource.new(access_key_id: access_key_id, secret_access_key: secret_access_key, region: region) + @bucket = @client.bucket(bucket) + end + + def upload(key, data) + object_for(key).put(body: data) + end + + def download(key) + if block_given? + stream(key, &block) + else + object_for(key).read + end + end + + def delete(key) + object_for(key).delete + end + + def exists?(key) + object_for(key).exists? + end + + + def byte_size(key) + object_for(key).head[:size] + end + + def checksum(key) + head = object_for(key).head + + # If the etag has no dashes, it's the MD5 + if !head.etag.include?("-") + head.etag.gsub('"', '') + # Check for md5 in metadata if it was uploaded via multipart + elsif md5sum = head.meta["md5sum"] + md5sum + # Otherwise, we don't have a digest yet for this key + else + nil + end + end + + + private + def object_for(key) + bucket.object(key) + end + + # Reads the object for the given key in chunks, yielding each to the block. + def stream(key, options = {}, &block) + object = object_for(key) + + chunk_size = 5242880 # 5 megabytes + offset = 0 + + while offset < object.content_length + yield object.read(options.merge(:range => "bytes=#{offset}-#{offset + chunk_size - 1}")) + offset += chunk_size + end + end +end From 29d65979f0db385d9872ba56221526638ad3db96 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 2 Jul 2017 16:47:54 +0200 Subject: [PATCH 030/289] Forget about verified IDs for now --- lib/active_file/blob.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 75e606b68b..e22976ed94 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -7,14 +7,9 @@ class ActiveFile::Blob < ActiveRecord::Base has_secure_token :key store :metadata, coder: JSON - class_attribute :verifier, default: -> { Rails.application.message_verifier('ActiveFile') } class_attribute :site class << self - def find_verified(signed_id) - find(verifier.verify(signed_id)) - end - def build_after_upload(data:, filename:, content_type: nil, metadata: nil) new.tap do |blob| blob.filename = name From 6d93b2dfe45b8542e2d4c00705f91157d4cf94bf Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 15:47:23 +0200 Subject: [PATCH 031/289] Example of how configuration could happen --- lib/active_file/config/sites.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 lib/active_file/config/sites.yml diff --git a/lib/active_file/config/sites.yml b/lib/active_file/config/sites.yml new file mode 100644 index 0000000000..bb550aed7a --- /dev/null +++ b/lib/active_file/config/sites.yml @@ -0,0 +1,25 @@ +# Configuration should be something like this: +# +# config/environments/development.rb +# config.active_file.site = :local +# +# config/environments/production.rb +# config.active_file.site = :amazon +local: + site: Disk + root: <%%= File.join(Dir.tmpdir, "active_file") %> + +amazon: + site: S3 + access_key_id: <%%= Rails.application.secrets.aws[:access_key_id] %> + secret_access_key: <%%= Rails.application.secrets.aws[:secret_access_key] %> + region: us-east-1 + bucket: <%= Rails.application.class.name.remove(/::Application$/).underscore %> + +google: + site: GCS + +mirror: + site: Mirror + primary: amazon + secondaries: google From 19a5191daae3d6083f906e81906905007daef1cb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 16:01:11 +0200 Subject: [PATCH 032/289] Simple idea for a mirror site --- lib/active_file/sites/mirror_site.rb | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lib/active_file/sites/mirror_site.rb diff --git a/lib/active_file/sites/mirror_site.rb b/lib/active_file/sites/mirror_site.rb new file mode 100644 index 0000000000..86dd906be7 --- /dev/null +++ b/lib/active_file/sites/mirror_site.rb @@ -0,0 +1,44 @@ +class ActiveFile::Sites::MirrorSite < ActiveFile::Site + attr_reader :sites + + def initialize(sites:) + @sites = sites + end + + def upload(key, data) + perform_across_sites :upload, key, data + end + + def download(key) + sites.detect { |site| site.exist?(key) }.download(key) + end + + def delete(key) + perform_across_sites :delete, key + end + + def exists?(key) + perform_across_sites(:exists?, key).any? + end + + + def byte_size(key) + primary_site.byte_size(key) + end + + def checksum(key) + primary_site.checksum(key) + end + + private + def primary_site + sites.first + end + + def perform_across_sites(method, **args) + # FIXME: Convert to be threaded + sites.collect do |site| + site.send method, **args + end + end +end From b3605d54fa661dfab2a52e3003d8a480ee48636c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 16:02:05 +0200 Subject: [PATCH 033/289] Use self-explaining named parameter --- lib/active_file/sites/disk_site.rb | 2 +- test/blob_test.rb | 2 +- test/disk_site_test.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index d61b6c3c5d..ee39b7a736 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -4,7 +4,7 @@ class ActiveFile::Sites::DiskSite < ActiveFile::Site attr_reader :root - def initialize(root) + def initialize(root:) @root = root end diff --git a/test/blob_test.rb b/test/blob_test.rb index 04d636e189..b18f0560f5 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -2,7 +2,7 @@ require "database/setup" require "active_file/blob" -ActiveFile::Blob.site = ActiveFile::Sites::DiskSite.new(File.join(Dir.tmpdir, "active_file")) +ActiveFile::Blob.site = ActiveFile::Sites::DiskSite.new(root: File.join(Dir.tmpdir, "active_file")) class ActiveFile::BlobTest < ActiveSupport::TestCase test "create after upload sets byte size and checksum" do diff --git a/test/disk_site_test.rb b/test/disk_site_test.rb index afae5d4683..c69a02bf45 100644 --- a/test/disk_site_test.rb +++ b/test/disk_site_test.rb @@ -9,7 +9,7 @@ class ActiveFile::DiskSiteTest < ActiveSupport::TestCase FIXTURE_FILE = StringIO.new("Hello world!") setup do - @site = ActiveFile::Sites::DiskSite.new(File.join(Dir.tmpdir, "active_file")) + @site = ActiveFile::Sites::DiskSite.new(root: File.join(Dir.tmpdir, "active_file")) @site.upload FIXTURE_KEY, FIXTURE_FILE FIXTURE_FILE.rewind end From ceae303c49523193216bef1a5ceb82940fb083c2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 16:04:14 +0200 Subject: [PATCH 034/289] Fix reference --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 737e47041f..7a59bcd18d 100644 --- a/README.md +++ b/README.md @@ -44,4 +44,4 @@ end ## License -Google Sign-In for Rails is released under the [MIT License](https://opensource.org/licenses/MIT). +Active File is released under the [MIT License](https://opensource.org/licenses/MIT). From 146a33bc88fd6d91f980b0ff31046222e701bcb0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 17:07:07 +0200 Subject: [PATCH 035/289] Missing CR --- lib/active_file.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_file.rb b/lib/active_file.rb index 6633ba9e82..b4b319fc8e 100644 --- a/lib/active_file.rb +++ b/lib/active_file.rb @@ -6,4 +6,4 @@ module ActiveFile autoload :Blob autoload :Site -end \ No newline at end of file +end From 18fe123cd11358491ecb301c9c2783a6fb2f7d10 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 17:07:17 +0200 Subject: [PATCH 036/289] Fix index --- lib/active_file/migration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_file/migration.rb b/lib/active_file/migration.rb index a6f398106e..041e29ef3b 100644 --- a/lib/active_file/migration.rb +++ b/lib/active_file/migration.rb @@ -8,7 +8,7 @@ def change t.string :checksum t.time :created_at - t.index [ :token ], unique: true + t.index [ :key ], unique: true end end end From 118b183be33227c74f63ee6b08a15b9367d29b3e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 18:38:47 +0200 Subject: [PATCH 037/289] Use rails_blobs for table to mimic routes prefix etc --- lib/active_file/blob.rb | 2 +- lib/active_file/migration.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 98bbcd057f..938f6d86b5 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -2,7 +2,7 @@ # Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at class ActiveFile::Blob < ActiveRecord::Base - self.table_name = "active_file_blobs" + self.table_name = "rails_blobs" has_secure_token :key store :metadata, coder: JSON diff --git a/lib/active_file/migration.rb b/lib/active_file/migration.rb index 041e29ef3b..1c87444dd4 100644 --- a/lib/active_file/migration.rb +++ b/lib/active_file/migration.rb @@ -1,9 +1,10 @@ class ActiveFile::CreateBlobs < ActiveRecord::Migration[5.1] def change - create_table :active_file_blobs do |t| + create_table :rails_blobs do |t| t.string :key t.string :filename t.string :content_type + t.text :metadata t.integer :byte_size t.string :checksum t.time :created_at From dca8d548b01407d21e660d7f9759d07d67329e07 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 20:13:50 +0200 Subject: [PATCH 038/289] Fix filename --- lib/active_file/blob.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 938f6d86b5..5a82edee65 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -1,4 +1,5 @@ require "active_file/site" +require "active_file/filename" # Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at class ActiveFile::Blob < ActiveRecord::Base @@ -29,7 +30,8 @@ def key end def filename - Filename.new(filename) + ActiveFile::Filename.new(self[:filename]) + end end From d2ff19c39c097aa17d16e33c8de981f43cd1ffa0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 20:14:28 +0200 Subject: [PATCH 039/289] WIP: Disk URLs --- lib/active_file/blob.rb | 3 ++ lib/active_file/disk_controller.rb | 20 +++++++++++++ lib/active_file/railtie.rb | 15 +++++++++- lib/active_file/sites/disk_site.rb | 48 ++++++++++++++++++++++++++++++ test/blob_test.rb | 12 +++++++- 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 lib/active_file/disk_controller.rb diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 5a82edee65..3cb98656f2 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -32,6 +32,9 @@ def key def filename ActiveFile::Filename.new(self[:filename]) end + + def url(disposition: :inline, expires_in: 5.minutes) + site.url key, disposition: disposition, expires_in: expires_in end diff --git a/lib/active_file/disk_controller.rb b/lib/active_file/disk_controller.rb new file mode 100644 index 0000000000..7d94b02f5c --- /dev/null +++ b/lib/active_file/disk_controller.rb @@ -0,0 +1,20 @@ +# FIXME: To be used by DiskSite#url +class ActiveFile::DiskController < ActionController::Base + def show + if verified_key.expired? + head :gone + else + blob = ActiveFile::Blob.find_by!(key: verified_key.to_s) + send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param + end + end + + private + def verified_key + ActiveFile::Sites::DiskSite::VerifiedKeyWithExpiration.new(params[:id]) + end + + def disposition_param + params[:disposition].presence_in(%w( inline attachment )) || 'inline' + end +end diff --git a/lib/active_file/railtie.rb b/lib/active_file/railtie.rb index e1b34f56cf..4398bb6072 100644 --- a/lib/active_file/railtie.rb +++ b/lib/active_file/railtie.rb @@ -1,6 +1,19 @@ require "rails/railtie" module ActiveFile - class Engine < ::Rails::Engine + class Railtie < Rails::Railtie # :nodoc: + config.action_cable = ActiveSupport::OrderedOptions.new + + config.eager_load_namespaces << ActiveFile + + initializer "action_cable.routes" do + require "active_file/disk_controller" + + config.after_initialize do |app| + app.routes.prepend do + get "/rails/blobs/:id" => "active_file/disk#show", as: :rails_disk_blob + end + end + end end end diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index ee39b7a736..da1e69df03 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -2,6 +2,42 @@ require "pathname" class ActiveFile::Sites::DiskSite < ActiveFile::Site + class_attribute :verifier, default: -> { Rails.application.message_verifier('ActiveFile::DiskSite') } + + class << self + def generate_verifiable_key(key, expires_in:) + VerifiedKeyWithExpiration + end + end + + class VerifiableKeyWithExpiration + def initialize(verifiable_key_with_expiration) + verified_key_with_expiration = ActiveFile::Sites::DiskSite.verify(verifiable_key_with_expiration) + + @key = verified_key_with_expiration[:key] + @expires_at = verified_key_with_expiration[:expires_at] + end + + def expired? + @expires_at && Time.now.utc > @expires_at + end + + def decoded + key + end + end + + class VerifiedKeyWithExpiration + def initialize(key, expires_in: nil) + @key = key + @expires_at = Time.now.utc.advance(sec: expires_in) + end + + def encoded + ActiveFile::Sites::DiskSite.verify.generate({ key: @key, expires_at: @expires_at }) + end + end + attr_reader :root def initialize(root:) @@ -38,6 +74,14 @@ def exists?(key) end + def url(key, disposition:, expires_in: nil) + if defined?(Rails) + Rails.application.routes.url_helpers.rails_disk_blob_path(key) + else + "/rails/blobs/#{key}" + end + end + def byte_size(key) File.size path_for(key) end @@ -48,6 +92,10 @@ def checksum(key) private + def verifiable_key_with_expiration(key, expires_in: nil) + verifier.generate key: key, expires_at: Time.now.utc.advance(sec: expires_in) + end + def path_for(key) File.join root, folder_for(key), key end diff --git a/test/blob_test.rb b/test/blob_test.rb index b18f0560f5..88b513c946 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -7,10 +7,20 @@ class ActiveFile::BlobTest < ActiveSupport::TestCase test "create after upload sets byte size and checksum" do data = "Hello world!" - blob = ActiveFile::Blob.create_after_upload! data: StringIO.new(data), filename: "hello.txt", content_type: "text/plain" + blob = create_blob data: data assert_equal data, blob.download assert_equal data.length, blob.byte_size assert_equal Digest::MD5.hexdigest(data), blob.checksum end + + test "url" do + blob = create_blob + assert_equal "/rails/blobs/#{blob.key}", blob.url + end + + private + def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") + ActiveFile::Blob.create_after_upload! data: StringIO.new(data), filename: filename, content_type: content_type + end end From dde68d4a8b6db22054cb218871b320eddbb3c546 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 20:14:41 +0200 Subject: [PATCH 040/289] Download extract from BC3 --- lib/active_file/download.rb | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 lib/active_file/download.rb diff --git a/lib/active_file/download.rb b/lib/active_file/download.rb new file mode 100644 index 0000000000..74f69a9dfc --- /dev/null +++ b/lib/active_file/download.rb @@ -0,0 +1,90 @@ +class ActiveFile::Download + # Sending .ai files as application/postscript to Safari opens them in a blank, grey screen. + # Downloading .ai as application/postscript files in Safari appends .ps to the extension. + # Sending HTML, SVG, XML and SWF files as binary closes XSS vulnerabilities. + # Sending JS files as binary avoids InvalidCrossOriginRequest without compromising security. + CONTENT_TYPES_TO_RENDER_AS_BINARY = %w( + text/html + text/javascript + image/svg+xml + application/postscript + application/x-shockwave-flash + text/xml + application/xml + application/xhtml+xml + ) + + BINARY_CONTENT_TYPE = 'application/octet-stream' + + def initialize(stored_file) + @stored_file = stored_file + end + + def headers(force_attachment: false) + { + x_accel_redirect: '/reproxy', + x_reproxy_url: reproxy_url, + content_type: content_type, + content_disposition: content_disposition(force_attachment), + x_frame_options: 'SAMEORIGIN' + } + end + + private + def reproxy_url + @stored_file.depot_location.paths.first + end + + def content_type + if @stored_file.content_type.in? CONTENT_TYPES_TO_RENDER_AS_BINARY + BINARY_CONTENT_TYPE + else + @stored_file.content_type + end + end + + def content_disposition(force_attachment = false) + if force_attachment || content_type == BINARY_CONTENT_TYPE + "attachment; #{escaped_filename}" + else + "inline; #{escaped_filename}" + end + end + + # RFC2231 encoding for UTF-8 filenames, with an ASCII fallback + # first for unsupported browsers (IE < 9, perhaps others?). + # http://greenbytes.de/tech/tc2231/#encoding-2231-fb + def escaped_filename + filename = @stored_file.filename.sanitized + ascii_filename = encode_ascii_filename(filename) + utf8_filename = encode_utf8_filename(filename) + "#{ascii_filename}; #{utf8_filename}" + end + + TRADITIONAL_PARAMETER_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/ + + def encode_ascii_filename(filename) + # There is no reliable way to escape special or non-Latin characters + # in a traditionally quoted Content-Disposition filename parameter. + # Settle for transliterating to ASCII, then percent-escaping special + # characters, excluding spaces. + filename = I18n.transliterate(filename) + filename = percent_escape(filename, TRADITIONAL_PARAMETER_ESCAPED_CHAR) + %(filename="#{filename}") + end + + RFC5987_PARAMETER_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/ + + def encode_utf8_filename(filename) + # RFC2231 filename parameters can simply be percent-escaped according + # to RFC5987. + filename = percent_escape(filename, RFC5987_PARAMETER_ESCAPED_CHAR) + %(filename*=UTF-8''#{filename}) + end + + def percent_escape(string, pattern) + string.gsub(pattern) do |char| + char.bytes.map { |byte| "%%%02X" % byte }.join("") + end + end +end From 4aac5e3fa207b8b047db5d3c96a97dca2a695214 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 21:06:09 +0200 Subject: [PATCH 041/289] Download disk blobs with verified URLs --- lib/active_file/disk_controller.rb | 13 +++--- lib/active_file/sites/disk_site.rb | 42 ++----------------- .../verified_key_with_expiration.rb | 15 +++++++ test/blob_test.rb | 9 ++-- test/test_helper.rb | 6 +++ test/verified_key_with_expiration_test.rb | 11 +++++ 6 files changed, 47 insertions(+), 49 deletions(-) create mode 100644 lib/active_file/verified_key_with_expiration.rb create mode 100644 test/verified_key_with_expiration_test.rb diff --git a/lib/active_file/disk_controller.rb b/lib/active_file/disk_controller.rb index 7d94b02f5c..b5a19fa5fc 100644 --- a/lib/active_file/disk_controller.rb +++ b/lib/active_file/disk_controller.rb @@ -1,17 +1,16 @@ -# FIXME: To be used by DiskSite#url class ActiveFile::DiskController < ActionController::Base def show - if verified_key.expired? - head :gone - else - blob = ActiveFile::Blob.find_by!(key: verified_key.to_s) + if key = decode_verified_key + blob = ActiveFile::Blob.find_by!(key: key) send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param + else + head :not_found end end private - def verified_key - ActiveFile::Sites::DiskSite::VerifiedKeyWithExpiration.new(params[:id]) + def decode_verified_key + ActiveFile::Sites::DiskSite::VerifiedKeyWithExpiration.decode(params[:id]) end def disposition_param diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index da1e69df03..be8a2437a1 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -2,42 +2,6 @@ require "pathname" class ActiveFile::Sites::DiskSite < ActiveFile::Site - class_attribute :verifier, default: -> { Rails.application.message_verifier('ActiveFile::DiskSite') } - - class << self - def generate_verifiable_key(key, expires_in:) - VerifiedKeyWithExpiration - end - end - - class VerifiableKeyWithExpiration - def initialize(verifiable_key_with_expiration) - verified_key_with_expiration = ActiveFile::Sites::DiskSite.verify(verifiable_key_with_expiration) - - @key = verified_key_with_expiration[:key] - @expires_at = verified_key_with_expiration[:expires_at] - end - - def expired? - @expires_at && Time.now.utc > @expires_at - end - - def decoded - key - end - end - - class VerifiedKeyWithExpiration - def initialize(key, expires_in: nil) - @key = key - @expires_at = Time.now.utc.advance(sec: expires_in) - end - - def encoded - ActiveFile::Sites::DiskSite.verify.generate({ key: @key, expires_at: @expires_at }) - end - end - attr_reader :root def initialize(root:) @@ -75,10 +39,12 @@ def exists?(key) def url(key, disposition:, expires_in: nil) + verified_key_with_expiration = ActiveFile::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) + if defined?(Rails) - Rails.application.routes.url_helpers.rails_disk_blob_path(key) + Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration) else - "/rails/blobs/#{key}" + "/rails/blobs/#{verified_key_with_expiration}" end end diff --git a/lib/active_file/verified_key_with_expiration.rb b/lib/active_file/verified_key_with_expiration.rb new file mode 100644 index 0000000000..601401278b --- /dev/null +++ b/lib/active_file/verified_key_with_expiration.rb @@ -0,0 +1,15 @@ +class ActiveFile::VerifiedKeyWithExpiration + class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveFile') : nil + + def self.encode(key, expires_in: nil) + verifier.generate([ key, expires_in ? Time.now.utc.advance(sec: expires_in) : nil ]) + end + + def self.decode(encoded_key) + key, expires_at = verifier.verified(encoded_key) + + if key + key if expires_at.nil? || Time.now.utc < expires_at + end + end +end diff --git a/test/blob_test.rb b/test/blob_test.rb index 88b513c946..ac54e0f2ca 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -2,8 +2,6 @@ require "database/setup" require "active_file/blob" -ActiveFile::Blob.site = ActiveFile::Sites::DiskSite.new(root: File.join(Dir.tmpdir, "active_file")) - class ActiveFile::BlobTest < ActiveSupport::TestCase test "create after upload sets byte size and checksum" do data = "Hello world!" @@ -14,9 +12,12 @@ class ActiveFile::BlobTest < ActiveSupport::TestCase assert_equal Digest::MD5.hexdigest(data), blob.checksum end - test "url" do + test "url expiring in 5 minutes" do blob = create_blob - assert_equal "/rails/blobs/#{blob.key}", blob.url + + travel_to Time.now do + assert_equal "/rails/blobs/#{ActiveFile::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}", blob.url + end end private diff --git a/test/test_helper.rb b/test/test_helper.rb index 0964774e00..5be2631ceb 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,3 +4,9 @@ require "byebug" require "active_file" + +require "active_file/site" +ActiveFile::Blob.site = ActiveFile::Sites::DiskSite.new(root: File.join(Dir.tmpdir, "active_file")) + +require "active_file/verified_key_with_expiration" +ActiveFile::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") diff --git a/test/verified_key_with_expiration_test.rb b/test/verified_key_with_expiration_test.rb new file mode 100644 index 0000000000..ac605a95e9 --- /dev/null +++ b/test/verified_key_with_expiration_test.rb @@ -0,0 +1,11 @@ +require "test_helper" +require "active_support/core_ext/securerandom" + +class ActiveFile::VerifiedKeyWithExpirationTest < ActiveSupport::TestCase + FIXTURE_KEY = SecureRandom.base58(24) + + test "without expiration" do + encoded_key = ActiveFile::VerifiedKeyWithExpiration.encode(FIXTURE_KEY) + assert_equal FIXTURE_KEY, ActiveFile::VerifiedKeyWithExpiration.decode(encoded_key) + end +end From 13193bf5ae21d30c58ab27d963596a5cae0fe45a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 21:07:02 +0200 Subject: [PATCH 042/289] No longer used --- lib/active_file/sites/disk_site.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index be8a2437a1..d0ec14cbe1 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -58,10 +58,6 @@ def checksum(key) private - def verifiable_key_with_expiration(key, expires_in: nil) - verifier.generate key: key, expires_at: Time.now.utc.advance(sec: expires_in) - end - def path_for(key) File.join root, folder_for(key), key end From 5f7b80a6d6fed524e8b41e9e465d360ddd6b8822 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 21:08:36 +0200 Subject: [PATCH 043/289] Match File.exist? --- lib/active_file/site.rb | 2 +- lib/active_file/sites/disk_site.rb | 2 +- lib/active_file/sites/mirror_site.rb | 4 ++-- lib/active_file/sites/s3_site.rb | 4 ++-- test/disk_site_test.rb | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 44010767dd..33494b916a 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -15,7 +15,7 @@ def delete(key) raise NotImplementedError end - def exists?(key) + def exist?(key) raise NotImplementedError end diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index d0ec14cbe1..7f151ed21b 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -33,7 +33,7 @@ def delete(key) File.delete path_for(key) end - def exists?(key) + def exist?(key) File.exist? path_for(key) end diff --git a/lib/active_file/sites/mirror_site.rb b/lib/active_file/sites/mirror_site.rb index 86dd906be7..f734f3ebf9 100644 --- a/lib/active_file/sites/mirror_site.rb +++ b/lib/active_file/sites/mirror_site.rb @@ -17,8 +17,8 @@ def delete(key) perform_across_sites :delete, key end - def exists?(key) - perform_across_sites(:exists?, key).any? + def exist?(key) + perform_across_sites(:exist?, key).any? end diff --git a/lib/active_file/sites/s3_site.rb b/lib/active_file/sites/s3_site.rb index 46c409405a..838163a23d 100644 --- a/lib/active_file/sites/s3_site.rb +++ b/lib/active_file/sites/s3_site.rb @@ -24,8 +24,8 @@ def delete(key) object_for(key).delete end - def exists?(key) - object_for(key).exists? + def exist?(key) + object_for(key).exist? end diff --git a/test/disk_site_test.rb b/test/disk_site_test.rb index c69a02bf45..198283a97b 100644 --- a/test/disk_site_test.rb +++ b/test/disk_site_test.rb @@ -32,13 +32,13 @@ class ActiveFile::DiskSiteTest < ActiveSupport::TestCase end test "existing" do - assert @site.exists?(FIXTURE_KEY) - assert_not @site.exists?(FIXTURE_KEY + "nonsense") + assert @site.exist?(FIXTURE_KEY) + assert_not @site.exist?(FIXTURE_KEY + "nonsense") end test "deleting" do @site.delete FIXTURE_KEY - assert_not @site.exists?(FIXTURE_KEY) + assert_not @site.exist?(FIXTURE_KEY) end test "sizing" do From b00ff22ca00f33c291b3beb2fe3818a2c30bab28 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 21:12:11 +0200 Subject: [PATCH 044/289] Fix and test expiration --- lib/active_file/verified_key_with_expiration.rb | 2 +- test/verified_key_with_expiration_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/active_file/verified_key_with_expiration.rb b/lib/active_file/verified_key_with_expiration.rb index 601401278b..b475f40f40 100644 --- a/lib/active_file/verified_key_with_expiration.rb +++ b/lib/active_file/verified_key_with_expiration.rb @@ -2,7 +2,7 @@ class ActiveFile::VerifiedKeyWithExpiration class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveFile') : nil def self.encode(key, expires_in: nil) - verifier.generate([ key, expires_in ? Time.now.utc.advance(sec: expires_in) : nil ]) + verifier.generate([ key, expires_in ? Time.now.utc.advance(seconds: expires_in) : nil ]) end def self.decode(encoded_key) diff --git a/test/verified_key_with_expiration_test.rb b/test/verified_key_with_expiration_test.rb index ac605a95e9..8f145590d0 100644 --- a/test/verified_key_with_expiration_test.rb +++ b/test/verified_key_with_expiration_test.rb @@ -8,4 +8,12 @@ class ActiveFile::VerifiedKeyWithExpirationTest < ActiveSupport::TestCase encoded_key = ActiveFile::VerifiedKeyWithExpiration.encode(FIXTURE_KEY) assert_equal FIXTURE_KEY, ActiveFile::VerifiedKeyWithExpiration.decode(encoded_key) end + + test "with expiration" do + encoded_key = ActiveFile::VerifiedKeyWithExpiration.encode(FIXTURE_KEY, expires_in: 1.minute) + assert_equal FIXTURE_KEY, ActiveFile::VerifiedKeyWithExpiration.decode(encoded_key) + + travel 2.minutes + assert_nil ActiveFile::VerifiedKeyWithExpiration.decode(encoded_key) + end end From a91a30260b0d474fd1704b4cde7ee7c3bd1d9a41 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 3 Jul 2017 23:19:51 +0200 Subject: [PATCH 045/289] Update for AWS S3 v2 API and test it when supplying the right ENVs --- lib/active_file/site.rb | 1 + lib/active_file/sites/s3_site.rb | 23 ++++------- test/s3_site_test.rb | 65 ++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 test/s3_site_test.rb diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 33494b916a..e44c0145a9 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -41,3 +41,4 @@ module ActiveFile::Sites end require "active_file/sites/disk_site" +require "active_file/sites/s3_site" diff --git a/lib/active_file/sites/s3_site.rb b/lib/active_file/sites/s3_site.rb index 838163a23d..4ede843cb4 100644 --- a/lib/active_file/sites/s3_site.rb +++ b/lib/active_file/sites/s3_site.rb @@ -16,7 +16,7 @@ def download(key) if block_given? stream(key, &block) else - object_for(key).read + object_for(key).get.body.read end end @@ -25,27 +25,20 @@ def delete(key) end def exist?(key) - object_for(key).exist? + object_for(key).exists? end + def url(key, disposition: :inline, expires_in: nil) + object_for(key).presigned_url(:get, expires_in: expires_in) + end + def byte_size(key) - object_for(key).head[:size] + object_for(key).size end def checksum(key) - head = object_for(key).head - - # If the etag has no dashes, it's the MD5 - if !head.etag.include?("-") - head.etag.gsub('"', '') - # Check for md5 in metadata if it was uploaded via multipart - elsif md5sum = head.meta["md5sum"] - md5sum - # Otherwise, we don't have a digest yet for this key - else - nil - end + object_for(key).etag.remove(/"/) end diff --git a/test/s3_site_test.rb b/test/s3_site_test.rb new file mode 100644 index 0000000000..bf7d4b0703 --- /dev/null +++ b/test/s3_site_test.rb @@ -0,0 +1,65 @@ +require "test_helper" +require "fileutils" +require "tmpdir" +require "active_support/core_ext/securerandom" +require "active_file/site" + +if ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"] && ENV["AWS_REGION"] && ENV["AWS_S3_BUCKET"] + class ActiveFile::S3SiteTest < ActiveSupport::TestCase + FIXTURE_KEY = SecureRandom.base58(24).to_s + FIXTURE_FILE = StringIO.new("Hello world!") + + setup do + @site = ActiveFile::Sites::S3Site.new( + access_key_id: ENV["AWS_ACCESS_KEY_ID"], + secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], + region: ENV["AWS_REGION"], + bucket: ENV["AWS_S3_BUCKET"] + ) + + @site.upload FIXTURE_KEY, FIXTURE_FILE + FIXTURE_FILE.rewind + end + + teardown do + @site.delete FIXTURE_KEY + FIXTURE_FILE.rewind + end + + test "uploading" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + @site.upload(key, StringIO.new(data)) + + assert_equal data, @site.download(key) + ensure + @site.delete key + end + end + + test "downloading" do + assert_equal FIXTURE_FILE.read, @site.download(FIXTURE_KEY) + end + + test "existing" do + assert @site.exist?(FIXTURE_KEY) + assert_not @site.exist?(FIXTURE_KEY + "nonsense") + end + + test "deleting" do + @site.delete FIXTURE_KEY + assert_not @site.exist?(FIXTURE_KEY) + end + + test "sizing" do + assert_equal FIXTURE_FILE.size, @site.byte_size(FIXTURE_KEY) + end + + test "checksumming" do + assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @site.checksum(FIXTURE_KEY) + end + end +else + puts "Skipping S3 Site tests because ENV variables are missing" +end From 52171ac2788183816cd5e8b145ab4408a7310978 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Mon, 3 Jul 2017 17:54:57 -0400 Subject: [PATCH 046/289] Add a Google Cloud Storage site --- Gemfile | 3 +- Gemfile.lock | 146 ++++++++++++++++++++++++++++++ lib/active_file/site.rb | 1 + lib/active_file/sites/gcs_site.rb | 43 +++++++++ test/gcs_site_test.rb | 64 +++++++++++++ 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 lib/active_file/sites/gcs_site.rb create mode 100644 test/gcs_site_test.rb diff --git a/Gemfile b/Gemfile index d502c9387e..af514f135e 100644 --- a/Gemfile +++ b/Gemfile @@ -6,4 +6,5 @@ gem 'rake' gem 'byebug' gem 'sqlite3' -gem 'aws-sdk' \ No newline at end of file +gem 'aws-sdk' +gem 'google-cloud' diff --git a/Gemfile.lock b/Gemfile.lock index 0438cb67fb..7078d544cb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,6 +23,8 @@ GEM i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) arel (8.0.0) aws-sdk (2.10.7) aws-sdk-resources (= 2.10.7) @@ -34,16 +36,159 @@ GEM aws-sigv4 (1.0.0) byebug (9.0.6) concurrent-ruby (1.0.5) + declarative (0.0.9) + declarative-option (0.1.0) + digest-crc (0.4.1) + faraday (0.12.1) + multipart-post (>= 1.2, < 3) globalid (0.4.0) activesupport (>= 4.2.0) + google-api-client (0.13.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.5) + httpclient (>= 2.8.1, < 3.0) + mime-types (~> 3.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + google-cloud (0.34.0) + google-cloud-bigquery (~> 0.27.0) + google-cloud-datastore (~> 1.0) + google-cloud-dns (~> 0.25.0) + google-cloud-error_reporting (~> 0.25.0) + google-cloud-language (~> 0.26.0) + google-cloud-logging (~> 1.0) + google-cloud-monitoring (~> 0.24.0) + google-cloud-pubsub (~> 0.25.0) + google-cloud-resource_manager (~> 0.26.0) + google-cloud-spanner (~> 0.21.0) + google-cloud-speech (~> 0.24.0) + google-cloud-storage (~> 1.2) + google-cloud-trace (~> 0.25.0) + google-cloud-translate (~> 1.0) + google-cloud-video_intelligence (~> 0.20.0) + google-cloud-vision (~> 0.24.0) + google-cloud-bigquery (0.27.0) + google-api-client (~> 0.13.0) + google-cloud-core (~> 1.0) + google-cloud-core (1.0.0) + google-cloud-env (~> 1.0) + googleauth (~> 0.5.1) + google-cloud-datastore (1.0.1) + google-cloud-core (~> 1.0) + google-gax (~> 0.8.0) + google-protobuf (~> 3.2.0) + google-cloud-dns (0.25.0) + google-api-client (~> 0.13.0) + google-cloud-core (~> 1.0) + zonefile (~> 1.04) + google-cloud-env (1.0.0) + faraday (~> 0.11) + google-cloud-error_reporting (0.25.0) + google-cloud-core (~> 1.0) + google-gax (~> 0.8.0) + stackdriver-core (~> 1.1) + google-cloud-language (0.26.2) + google-cloud-core (~> 1.0) + google-gax (~> 0.8.2) + google-cloud-logging (1.1.0) + google-cloud-core (~> 1.0) + google-gax (~> 0.8.0) + stackdriver-core (~> 1.1) + google-cloud-monitoring (0.24.0) + google-gax (~> 0.8.0) + google-cloud-pubsub (0.25.0) + google-cloud-core (~> 1.0) + google-gax (~> 0.8.0) + grpc-google-iam-v1 (~> 0.6.8) + google-cloud-resource_manager (0.26.0) + google-api-client (~> 0.13.0) + google-cloud-core (~> 1.0) + google-cloud-spanner (0.21.0) + concurrent-ruby (~> 1.0) + google-cloud-core (~> 1.0) + google-gax (~> 0.8.1) + grpc (~> 1.1) + grpc-google-iam-v1 (~> 0.6.8) + google-cloud-speech (0.24.0) + google-cloud-core (~> 1.0) + google-gax (~> 0.8.2) + google-cloud-storage (1.2.0) + digest-crc (~> 0.4) + google-api-client (~> 0.13.0) + google-cloud-core (~> 1.0) + google-cloud-trace (0.25.0) + google-cloud-core (~> 1.0) + google-gax (~> 0.8.0) + stackdriver-core (~> 1.1) + google-cloud-translate (1.0.0) + google-cloud-core (~> 1.0) + googleauth (~> 0.5.1) + google-cloud-video_intelligence (0.20.0) + google-gax (~> 0.8.0) + google-cloud-vision (0.24.0) + google-cloud-core (~> 1.0) + google-gax (~> 0.8.0) + google-gax (0.8.4) + google-protobuf (~> 3.2) + googleapis-common-protos (~> 1.3.5) + googleauth (~> 0.5.1) + grpc (~> 1.0) + rly (~> 0.2.3) + google-protobuf (3.2.0.2) + googleapis-common-protos (1.3.5) + google-protobuf (~> 3.2) + grpc (~> 1.0) + googleauth (0.5.1) + faraday (~> 0.9) + jwt (~> 1.4) + logging (~> 2.0) + memoist (~> 0.12) + multi_json (~> 1.11) + os (~> 0.9) + signet (~> 0.7) + grpc (1.4.1) + google-protobuf (~> 3.1) + googleauth (~> 0.5.1) + grpc-google-iam-v1 (0.6.8) + googleapis-common-protos (~> 1.3.1) + googleauth (~> 0.5.1) + grpc (~> 1.0) + httpclient (2.8.3) i18n (0.8.4) jmespath (1.3.1) + jwt (1.5.6) + little-plugger (1.1.4) + logging (2.2.2) + little-plugger (~> 1.1) + multi_json (~> 1.10) + memoist (0.16.0) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) minitest (5.10.2) + multi_json (1.12.1) + multipart-post (2.0.0) + os (0.9.6) + public_suffix (2.0.5) rake (12.0.0) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.0.2) + rly (0.2.3) + signet (0.7.3) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (~> 1.5) + multi_json (~> 1.10) sqlite3 (1.3.13) + stackdriver-core (1.1.0) thread_safe (0.3.6) tzinfo (1.2.3) thread_safe (~> 0.1) + uber (0.1.0) + zonefile (1.06) PLATFORMS ruby @@ -53,6 +198,7 @@ DEPENDENCIES aws-sdk bundler (~> 1.15) byebug + google-cloud rake sqlite3 diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index e44c0145a9..3340c16620 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -41,4 +41,5 @@ module ActiveFile::Sites end require "active_file/sites/disk_site" +require "active_file/sites/gcs_site" require "active_file/sites/s3_site" diff --git a/lib/active_file/sites/gcs_site.rb b/lib/active_file/sites/gcs_site.rb new file mode 100644 index 0000000000..d9164621c2 --- /dev/null +++ b/lib/active_file/sites/gcs_site.rb @@ -0,0 +1,43 @@ +require "google/cloud/storage" + +class ActiveFile::Sites::GCSSite < ActiveFile::Site + attr_reader :client, :bucket + + def initialize(project:, keyfile:, bucket:) + @client = Google::Cloud::Storage.new(project: project, keyfile: keyfile) + @bucket = @client.bucket(bucket) + end + + def upload(key, data) + bucket.create_file(data, key) + end + + def download(key) + io = file_for(key).download + io.rewind + io.read + end + + def delete(key) + file_for(key).try(:delete) + end + + def exist?(key) + file_for(key).present? + end + + + def byte_size(key) + file_for(key).size + end + + def checksum(key) + file_for(key).md5.unpack("m0").first.unpack("H*").first + end + + + private + def file_for(key) + bucket.file(key) + end +end diff --git a/test/gcs_site_test.rb b/test/gcs_site_test.rb new file mode 100644 index 0000000000..7363850ce7 --- /dev/null +++ b/test/gcs_site_test.rb @@ -0,0 +1,64 @@ +require "test_helper" +require "fileutils" +require "tmpdir" +require "active_support/core_ext/securerandom" +require "active_file/site" + +if ENV["GCS_PROJECT"] && ENV["GCS_KEYFILE"] && ENV["GCS_BUCKET"] + class ActiveFile::GCSSiteTest < ActiveSupport::TestCase + FIXTURE_KEY = SecureRandom.base58(24).to_s + FIXTURE_FILE = StringIO.new("Hello world!") + + setup do + @site = ActiveFile::Sites::GCSSite.new( + project: ENV["GCS_PROJECT"], + keyfile: ENV["GCS_KEYFILE"], + bucket: ENV["GCS_BUCKET"] + ) + + @site.upload FIXTURE_KEY, FIXTURE_FILE + FIXTURE_FILE.rewind + end + + teardown do + @site.delete FIXTURE_KEY + FIXTURE_FILE.rewind + end + + test "uploading" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + @site.upload(key, StringIO.new(data)) + + assert_equal data, @site.download(key) + ensure + @site.delete key + end + end + + test "downloading" do + assert_equal FIXTURE_FILE.read, @site.download(FIXTURE_KEY) + end + + test "existing" do + assert @site.exist?(FIXTURE_KEY) + assert_not @site.exist?(FIXTURE_KEY + "nonsense") + end + + test "deleting" do + @site.delete FIXTURE_KEY + assert_not @site.exist?(FIXTURE_KEY) + end + + test "sizing" do + assert_equal FIXTURE_FILE.size, @site.byte_size(FIXTURE_KEY) + end + + test "checksumming" do + assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @site.checksum(FIXTURE_KEY) + end + end +else + puts "Skipping GCS Site tests because ENV variables are missing" +end From e55c885d9ca4df7d9d146db6f3ad9773d84f7935 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Mon, 3 Jul 2017 17:56:51 -0400 Subject: [PATCH 047/289] Shush noisy Ruby interpreter warnings --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index a61ad18bca..aec4d19100 100644 --- a/Rakefile +++ b/Rakefile @@ -5,6 +5,7 @@ require "rake/testtask" Rake::TestTask.new do |test| test.libs << "test" test.test_files = FileList["test/*_test.rb"] + test.warning = false end task default: :test From d7e877be2445ded5effef4cecadeab90ef0be9f1 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Mon, 3 Jul 2017 18:02:26 -0400 Subject: [PATCH 048/289] Remove unnecessary requires --- test/gcs_site_test.rb | 2 -- test/s3_site_test.rb | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/test/gcs_site_test.rb b/test/gcs_site_test.rb index 7363850ce7..468454a6fb 100644 --- a/test/gcs_site_test.rb +++ b/test/gcs_site_test.rb @@ -1,6 +1,4 @@ require "test_helper" -require "fileutils" -require "tmpdir" require "active_support/core_ext/securerandom" require "active_file/site" diff --git a/test/s3_site_test.rb b/test/s3_site_test.rb index bf7d4b0703..24a890e3ec 100644 --- a/test/s3_site_test.rb +++ b/test/s3_site_test.rb @@ -1,6 +1,4 @@ require "test_helper" -require "fileutils" -require "tmpdir" require "active_support/core_ext/securerandom" require "active_file/site" @@ -55,7 +53,7 @@ class ActiveFile::S3SiteTest < ActiveSupport::TestCase test "sizing" do assert_equal FIXTURE_FILE.size, @site.byte_size(FIXTURE_KEY) end - + test "checksumming" do assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @site.checksum(FIXTURE_KEY) end From a146858a143ab14cf1af3bda3e0705c63404c7f1 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 14:54:38 +0200 Subject: [PATCH 049/289] Extract explaining method --- lib/active_file/sites/gcs_site.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/active_file/sites/gcs_site.rb b/lib/active_file/sites/gcs_site.rb index d9164621c2..71f065c608 100644 --- a/lib/active_file/sites/gcs_site.rb +++ b/lib/active_file/sites/gcs_site.rb @@ -32,7 +32,7 @@ def byte_size(key) end def checksum(key) - file_for(key).md5.unpack("m0").first.unpack("H*").first + convert_to_hex base64: file_for(key).md5 end @@ -40,4 +40,8 @@ def checksum(key) def file_for(key) bucket.file(key) end + + def convert_to_hex(base64:) + base64.unpack("m0").first.unpack("H*").first + end end From 2a2f8ca521e9ffaebd4227940b802ce88fdbb02d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 15:01:55 +0200 Subject: [PATCH 050/289] Extract explaining methods --- .../verified_key_with_expiration.rb | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/active_file/verified_key_with_expiration.rb b/lib/active_file/verified_key_with_expiration.rb index b475f40f40..e9e811d364 100644 --- a/lib/active_file/verified_key_with_expiration.rb +++ b/lib/active_file/verified_key_with_expiration.rb @@ -1,15 +1,24 @@ class ActiveFile::VerifiedKeyWithExpiration class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveFile') : nil - def self.encode(key, expires_in: nil) - verifier.generate([ key, expires_in ? Time.now.utc.advance(seconds: expires_in) : nil ]) - end - - def self.decode(encoded_key) - key, expires_at = verifier.verified(encoded_key) - - if key - key if expires_at.nil? || Time.now.utc < expires_at + class << self + def encode(key, expires_in: nil) + verifier.generate([ key, expires_at(expires_in) ]) end + + def decode(encoded_key) + key, expires_at = verifier.verified(encoded_key) + + key if key && fresh?(expires_at) + end + + private + def expires_at(expires_in) + expires_in ? Time.now.utc.advance(seconds: expires_in) : nil + end + + def fresh?(expires_at) + expires_at.nil? || Time.now.utc < expires_at + end end end From e10f62f092e14650ba1a07fa3d0751109933cafe Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 15:28:47 +0200 Subject: [PATCH 051/289] Extract shared tests --- Rakefile | 2 +- lib/active_file/sites/disk_site.rb | 2 +- test/disk_site_test.rb | 51 --------------- test/s3_site_test.rb | 63 ------------------- test/sites/disk_site_test.rb | 8 +++ test/sites/gcs_site_test.rb | 13 ++++ test/sites/s3_site_test.rb | 16 +++++ .../shared_site_tests.rb} | 22 +++---- 8 files changed, 47 insertions(+), 130 deletions(-) delete mode 100644 test/disk_site_test.rb delete mode 100644 test/s3_site_test.rb create mode 100644 test/sites/disk_site_test.rb create mode 100644 test/sites/gcs_site_test.rb create mode 100644 test/sites/s3_site_test.rb rename test/{gcs_site_test.rb => sites/shared_site_tests.rb} (69%) diff --git a/Rakefile b/Rakefile index aec4d19100..f0baf50163 100644 --- a/Rakefile +++ b/Rakefile @@ -4,7 +4,7 @@ require "rake/testtask" Rake::TestTask.new do |test| test.libs << "test" - test.test_files = FileList["test/*_test.rb"] + test.test_files = FileList["test/**/*_test.rb"] test.warning = false end diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index 7f151ed21b..41b883498a 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -30,7 +30,7 @@ def download(key) end def delete(key) - File.delete path_for(key) + File.delete path_for(key) rescue Errno::ENOENT # Ignore files already deleted end def exist?(key) diff --git a/test/disk_site_test.rb b/test/disk_site_test.rb deleted file mode 100644 index 198283a97b..0000000000 --- a/test/disk_site_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require "test_helper" -require "fileutils" -require "tmpdir" -require "active_support/core_ext/securerandom" -require "active_file/site" - -class ActiveFile::DiskSiteTest < ActiveSupport::TestCase - FIXTURE_KEY = SecureRandom.base58(24) - FIXTURE_FILE = StringIO.new("Hello world!") - - setup do - @site = ActiveFile::Sites::DiskSite.new(root: File.join(Dir.tmpdir, "active_file")) - @site.upload FIXTURE_KEY, FIXTURE_FILE - FIXTURE_FILE.rewind - end - - teardown do - FileUtils.rm_rf @site.root - FIXTURE_FILE.rewind - end - - test "uploading" do - key = SecureRandom.base58(24) - data = "Something else entirely!" - @site.upload(key, StringIO.new(data)) - - assert_equal data, @site.download(key) - end - - test "downloading" do - assert_equal FIXTURE_FILE.read, @site.download(FIXTURE_KEY) - end - - test "existing" do - assert @site.exist?(FIXTURE_KEY) - assert_not @site.exist?(FIXTURE_KEY + "nonsense") - end - - test "deleting" do - @site.delete FIXTURE_KEY - assert_not @site.exist?(FIXTURE_KEY) - end - - test "sizing" do - assert_equal FIXTURE_FILE.size, @site.byte_size(FIXTURE_KEY) - end - - test "checksumming" do - assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @site.checksum(FIXTURE_KEY) - end -end diff --git a/test/s3_site_test.rb b/test/s3_site_test.rb deleted file mode 100644 index 24a890e3ec..0000000000 --- a/test/s3_site_test.rb +++ /dev/null @@ -1,63 +0,0 @@ -require "test_helper" -require "active_support/core_ext/securerandom" -require "active_file/site" - -if ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"] && ENV["AWS_REGION"] && ENV["AWS_S3_BUCKET"] - class ActiveFile::S3SiteTest < ActiveSupport::TestCase - FIXTURE_KEY = SecureRandom.base58(24).to_s - FIXTURE_FILE = StringIO.new("Hello world!") - - setup do - @site = ActiveFile::Sites::S3Site.new( - access_key_id: ENV["AWS_ACCESS_KEY_ID"], - secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], - region: ENV["AWS_REGION"], - bucket: ENV["AWS_S3_BUCKET"] - ) - - @site.upload FIXTURE_KEY, FIXTURE_FILE - FIXTURE_FILE.rewind - end - - teardown do - @site.delete FIXTURE_KEY - FIXTURE_FILE.rewind - end - - test "uploading" do - begin - key = SecureRandom.base58(24) - data = "Something else entirely!" - @site.upload(key, StringIO.new(data)) - - assert_equal data, @site.download(key) - ensure - @site.delete key - end - end - - test "downloading" do - assert_equal FIXTURE_FILE.read, @site.download(FIXTURE_KEY) - end - - test "existing" do - assert @site.exist?(FIXTURE_KEY) - assert_not @site.exist?(FIXTURE_KEY + "nonsense") - end - - test "deleting" do - @site.delete FIXTURE_KEY - assert_not @site.exist?(FIXTURE_KEY) - end - - test "sizing" do - assert_equal FIXTURE_FILE.size, @site.byte_size(FIXTURE_KEY) - end - - test "checksumming" do - assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @site.checksum(FIXTURE_KEY) - end - end -else - puts "Skipping S3 Site tests because ENV variables are missing" -end diff --git a/test/sites/disk_site_test.rb b/test/sites/disk_site_test.rb new file mode 100644 index 0000000000..0956f08528 --- /dev/null +++ b/test/sites/disk_site_test.rb @@ -0,0 +1,8 @@ +require "tmpdir" +require "sites/shared_site_tests" + +class ActiveFile::Sites::DiskSiteTest < ActiveSupport::TestCase + SITE = ActiveFile::Sites::DiskSite.new(root: File.join(Dir.tmpdir, "active_file")) + + include ActiveFile::Sites::SharedSiteTests +end diff --git a/test/sites/gcs_site_test.rb b/test/sites/gcs_site_test.rb new file mode 100644 index 0000000000..e43223c28d --- /dev/null +++ b/test/sites/gcs_site_test.rb @@ -0,0 +1,13 @@ +require "sites/shared_site_tests" + +if ENV["GCS_PROJECT"] && ENV["GCS_KEYFILE"] && ENV["GCS_BUCKET"] + class ActiveFile::Sites::GCSSiteTest < ActiveSupport::TestCase + SITE = ActiveFile::Sites::GCSSite.new( + project: ENV["GCS_PROJECT"], keyfile: ENV["GCS_KEYFILE"], bucket: ENV["GCS_BUCKET"] + ) + + include ActiveFile::Sites::SharedSiteTests + end +else + puts "Skipping GCS Site tests because ENV variables are missing" +end diff --git a/test/sites/s3_site_test.rb b/test/sites/s3_site_test.rb new file mode 100644 index 0000000000..9e165f0dea --- /dev/null +++ b/test/sites/s3_site_test.rb @@ -0,0 +1,16 @@ +require "sites/shared_site_tests" + +if ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"] && ENV["AWS_REGION"] && ENV["AWS_S3_BUCKET"] + class ActiveFile::Sites::S3SiteTest < ActiveSupport::TestCase + SITE = ActiveFile::Sites::S3Site.new( + access_key_id: ENV["AWS_ACCESS_KEY_ID"], + secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], + region: ENV["AWS_REGION"], + bucket: ENV["AWS_S3_BUCKET"] + ) + + include ActiveFile::Sites::SharedSiteTests + end +else + puts "Skipping S3 Site tests because ENV variables are missing" +end diff --git a/test/gcs_site_test.rb b/test/sites/shared_site_tests.rb similarity index 69% rename from test/gcs_site_test.rb rename to test/sites/shared_site_tests.rb index 468454a6fb..fd900be4a4 100644 --- a/test/gcs_site_test.rb +++ b/test/sites/shared_site_tests.rb @@ -1,19 +1,15 @@ require "test_helper" require "active_support/core_ext/securerandom" -require "active_file/site" -if ENV["GCS_PROJECT"] && ENV["GCS_KEYFILE"] && ENV["GCS_BUCKET"] - class ActiveFile::GCSSiteTest < ActiveSupport::TestCase - FIXTURE_KEY = SecureRandom.base58(24).to_s - FIXTURE_FILE = StringIO.new("Hello world!") +module ActiveFile::Sites::SharedSiteTests + extend ActiveSupport::Concern + + FIXTURE_KEY = SecureRandom.base58(24) + FIXTURE_FILE = StringIO.new("Hello world!") + included do setup do - @site = ActiveFile::Sites::GCSSite.new( - project: ENV["GCS_PROJECT"], - keyfile: ENV["GCS_KEYFILE"], - bucket: ENV["GCS_BUCKET"] - ) - + @site = self.class.const_get(:SITE) @site.upload FIXTURE_KEY, FIXTURE_FILE FIXTURE_FILE.rewind end @@ -52,11 +48,9 @@ class ActiveFile::GCSSiteTest < ActiveSupport::TestCase test "sizing" do assert_equal FIXTURE_FILE.size, @site.byte_size(FIXTURE_KEY) end - + test "checksumming" do assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @site.checksum(FIXTURE_KEY) end end -else - puts "Skipping GCS Site tests because ENV variables are missing" end From 8da081c36fa2523b8b44d5952fc97c8c012df54e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 15:59:53 +0200 Subject: [PATCH 052/289] Extract cloud site configuration to gitignored YAML file --- test/sites/.gitignore | 1 + test/sites/configurations-example.yml | 11 +++++++++++ test/sites/gcs_site_test.rb | 8 +++----- test/sites/s3_site_test.rb | 11 +++-------- test/sites/shared_site_tests.rb | 9 ++++++++- 5 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 test/sites/.gitignore create mode 100644 test/sites/configurations-example.yml diff --git a/test/sites/.gitignore b/test/sites/.gitignore new file mode 100644 index 0000000000..c102131f3d --- /dev/null +++ b/test/sites/.gitignore @@ -0,0 +1 @@ +configurations.yml diff --git a/test/sites/configurations-example.yml b/test/sites/configurations-example.yml new file mode 100644 index 0000000000..031197342a --- /dev/null +++ b/test/sites/configurations-example.yml @@ -0,0 +1,11 @@ +# Copy this file to configurations.yml and edit the credentials to match your IAM test account and bucket +s3: + access_key_id: + secret_access_key: + region: + bucket: + +gcs: + project: + keyfile: + bucket: diff --git a/test/sites/gcs_site_test.rb b/test/sites/gcs_site_test.rb index e43223c28d..fbf6c4a242 100644 --- a/test/sites/gcs_site_test.rb +++ b/test/sites/gcs_site_test.rb @@ -1,13 +1,11 @@ require "sites/shared_site_tests" -if ENV["GCS_PROJECT"] && ENV["GCS_KEYFILE"] && ENV["GCS_BUCKET"] +if SITE_CONFIGURATIONS[:gcs] class ActiveFile::Sites::GCSSiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Sites::GCSSite.new( - project: ENV["GCS_PROJECT"], keyfile: ENV["GCS_KEYFILE"], bucket: ENV["GCS_BUCKET"] - ) + SITE = ActiveFile::Sites::GCSSite.new(SITE_CONFIGURATIONS[:gcs]) include ActiveFile::Sites::SharedSiteTests end else - puts "Skipping GCS Site tests because ENV variables are missing" + puts "Skipping GCS Site tests because no GCS configuration was supplied" end diff --git a/test/sites/s3_site_test.rb b/test/sites/s3_site_test.rb index 9e165f0dea..12f5d084f6 100644 --- a/test/sites/s3_site_test.rb +++ b/test/sites/s3_site_test.rb @@ -1,16 +1,11 @@ require "sites/shared_site_tests" -if ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"] && ENV["AWS_REGION"] && ENV["AWS_S3_BUCKET"] +if SITE_CONFIGURATIONS[:s3] class ActiveFile::Sites::S3SiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Sites::S3Site.new( - access_key_id: ENV["AWS_ACCESS_KEY_ID"], - secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], - region: ENV["AWS_REGION"], - bucket: ENV["AWS_S3_BUCKET"] - ) + SITE = ActiveFile::Sites::S3Site.new(SITE_CONFIGURATIONS[:s3]) include ActiveFile::Sites::SharedSiteTests end else - puts "Skipping S3 Site tests because ENV variables are missing" + puts "Skipping S3 Site tests because no S3 configuration was supplied" end diff --git a/test/sites/shared_site_tests.rb b/test/sites/shared_site_tests.rb index fd900be4a4..de28d7ae63 100644 --- a/test/sites/shared_site_tests.rb +++ b/test/sites/shared_site_tests.rb @@ -1,9 +1,16 @@ require "test_helper" require "active_support/core_ext/securerandom" +require "yaml" + +SITE_CONFIGURATIONS = begin + YAML.load_file(File.expand_path("../configurations.yml", __FILE__)).deep_symbolize_keys +rescue Errno::ENOENT + puts "Missing site configuration file in test/sites/configurations.yml" +end module ActiveFile::Sites::SharedSiteTests extend ActiveSupport::Concern - + FIXTURE_KEY = SecureRandom.base58(24) FIXTURE_FILE = StringIO.new("Hello world!") From ccaba581c0cf8653f61ce212667eaa1cc6f0a28e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 16:05:06 +0200 Subject: [PATCH 053/289] Differentiate between io streams and read data --- lib/active_file/blob.rb | 12 ++++++------ lib/active_file/site.rb | 2 +- lib/active_file/sites/disk_site.rb | 4 ++-- lib/active_file/sites/gcs_site.rb | 4 ++-- lib/active_file/sites/mirror_site.rb | 4 ++-- lib/active_file/sites/s3_site.rb | 4 ++-- test/blob_test.rb | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 3cb98656f2..4af0551f99 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -11,16 +11,16 @@ class ActiveFile::Blob < ActiveRecord::Base class_attribute :site class << self - def build_after_upload(data:, filename:, content_type: nil, metadata: nil) + def build_after_upload(io:, filename:, content_type: nil, metadata: nil) new.tap do |blob| blob.filename = name blob.content_type = content_type # Marcel::MimeType.for(data, name: name, declared_type: content_type) - blob.upload data + blob.upload io end end - def create_after_upload!(data:, filename:, content_type: nil, metadata: nil) - build_after_upload(data: data, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!) + def create_after_upload!(io:, filename:, content_type: nil, metadata: nil) + build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!) end end @@ -38,8 +38,8 @@ def url(disposition: :inline, expires_in: 5.minutes) end - def upload(data) - site.upload(key, data) + def upload(io) + site.upload(key, io) self.checksum = site.checksum(key) self.byte_size = site.byte_size(key) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 3340c16620..eb72ef39d4 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -3,7 +3,7 @@ class ActiveFile::Site def initialize end - def upload(key, data) + def upload(key, io) raise NotImplementedError end diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/sites/disk_site.rb index 41b883498a..e5572073ed 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/sites/disk_site.rb @@ -9,9 +9,9 @@ def initialize(root:) end - def upload(key, data) + def upload(key, io) File.open(make_path_for(key), "wb") do |file| - while chunk = data.read(65536) + while chunk = io.read(65536) file.write(chunk) end end diff --git a/lib/active_file/sites/gcs_site.rb b/lib/active_file/sites/gcs_site.rb index 71f065c608..f5f8696f56 100644 --- a/lib/active_file/sites/gcs_site.rb +++ b/lib/active_file/sites/gcs_site.rb @@ -8,8 +8,8 @@ def initialize(project:, keyfile:, bucket:) @bucket = @client.bucket(bucket) end - def upload(key, data) - bucket.create_file(data, key) + def upload(key, io) + bucket.create_file(io, key) end def download(key) diff --git a/lib/active_file/sites/mirror_site.rb b/lib/active_file/sites/mirror_site.rb index f734f3ebf9..051d139af1 100644 --- a/lib/active_file/sites/mirror_site.rb +++ b/lib/active_file/sites/mirror_site.rb @@ -5,8 +5,8 @@ def initialize(sites:) @sites = sites end - def upload(key, data) - perform_across_sites :upload, key, data + def upload(key, io) + perform_across_sites :upload, key, io end def download(key) diff --git a/lib/active_file/sites/s3_site.rb b/lib/active_file/sites/s3_site.rb index 4ede843cb4..e13b609881 100644 --- a/lib/active_file/sites/s3_site.rb +++ b/lib/active_file/sites/s3_site.rb @@ -8,8 +8,8 @@ def initialize(access_key_id:, secret_access_key:, region:, bucket:) @bucket = @client.bucket(bucket) end - def upload(key, data) - object_for(key).put(body: data) + def upload(key, io) + object_for(key).put(body: io) end def download(key) diff --git a/test/blob_test.rb b/test/blob_test.rb index ac54e0f2ca..c726555dc6 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -22,6 +22,6 @@ class ActiveFile::BlobTest < ActiveSupport::TestCase private def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") - ActiveFile::Blob.create_after_upload! data: StringIO.new(data), filename: filename, content_type: content_type + ActiveFile::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type end end From efd950ae706cfbb55dffebd5d0c85e30acfd7a45 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 16:44:50 +0200 Subject: [PATCH 054/289] Use lazy-loaded factory method for site configuration --- Gemfile | 5 +++-- lib/active_file/disk_controller.rb | 2 +- lib/active_file/site.rb | 14 +++++++------- lib/active_file/{sites => site}/disk_site.rb | 2 +- lib/active_file/{sites => site}/gcs_site.rb | 2 +- lib/active_file/{sites => site}/mirror_site.rb | 2 +- lib/active_file/{sites => site}/s3_site.rb | 2 +- test/{sites => site}/.gitignore | 0 test/{sites => site}/configurations-example.yml | 0 test/site/disk_site_test.rb | 8 ++++++++ test/site/gcs_site_test.rb | 11 +++++++++++ test/site/s3_site_test.rb | 11 +++++++++++ test/{sites => site}/shared_site_tests.rb | 2 +- test/sites/disk_site_test.rb | 8 -------- test/sites/gcs_site_test.rb | 11 ----------- test/sites/s3_site_test.rb | 11 ----------- test/test_helper.rb | 2 +- 17 files changed, 47 insertions(+), 46 deletions(-) rename lib/active_file/{sites => site}/disk_site.rb (96%) rename lib/active_file/{sites => site}/gcs_site.rb (93%) rename lib/active_file/{sites => site}/mirror_site.rb (92%) rename lib/active_file/{sites => site}/s3_site.rb (96%) rename test/{sites => site}/.gitignore (100%) rename test/{sites => site}/configurations-example.yml (100%) create mode 100644 test/site/disk_site_test.rb create mode 100644 test/site/gcs_site_test.rb create mode 100644 test/site/s3_site_test.rb rename test/{sites => site}/shared_site_tests.rb (97%) delete mode 100644 test/sites/disk_site_test.rb delete mode 100644 test/sites/gcs_site_test.rb delete mode 100644 test/sites/s3_site_test.rb diff --git a/Gemfile b/Gemfile index af514f135e..60b2596c53 100644 --- a/Gemfile +++ b/Gemfile @@ -6,5 +6,6 @@ gem 'rake' gem 'byebug' gem 'sqlite3' -gem 'aws-sdk' -gem 'google-cloud' + +gem 'aws-sdk', require: false +gem 'google-cloud', require: false diff --git a/lib/active_file/disk_controller.rb b/lib/active_file/disk_controller.rb index b5a19fa5fc..f016c90fc5 100644 --- a/lib/active_file/disk_controller.rb +++ b/lib/active_file/disk_controller.rb @@ -10,7 +10,7 @@ def show private def decode_verified_key - ActiveFile::Sites::DiskSite::VerifiedKeyWithExpiration.decode(params[:id]) + ActiveFile::Site::DiskSite::VerifiedKeyWithExpiration.decode(params[:id]) end def disposition_param diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index eb72ef39d4..31640695d5 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -1,5 +1,12 @@ # Abstract class serving as an interface for concrete sites. class ActiveFile::Site + def self.configure(site, **options) + begin + require "active_file/site/#{site.to_s.downcase}_site" + ActiveFile::Site.const_get(:"#{site}Site").new(**options) + end + end + def initialize end @@ -36,10 +43,3 @@ def move(from:, to:) raise NotImplementedError end end - -module ActiveFile::Sites -end - -require "active_file/sites/disk_site" -require "active_file/sites/gcs_site" -require "active_file/sites/s3_site" diff --git a/lib/active_file/sites/disk_site.rb b/lib/active_file/site/disk_site.rb similarity index 96% rename from lib/active_file/sites/disk_site.rb rename to lib/active_file/site/disk_site.rb index e5572073ed..1fa77029c7 100644 --- a/lib/active_file/sites/disk_site.rb +++ b/lib/active_file/site/disk_site.rb @@ -1,7 +1,7 @@ require "fileutils" require "pathname" -class ActiveFile::Sites::DiskSite < ActiveFile::Site +class ActiveFile::Site::DiskSite < ActiveFile::Site attr_reader :root def initialize(root:) diff --git a/lib/active_file/sites/gcs_site.rb b/lib/active_file/site/gcs_site.rb similarity index 93% rename from lib/active_file/sites/gcs_site.rb rename to lib/active_file/site/gcs_site.rb index f5f8696f56..c5f3d634cf 100644 --- a/lib/active_file/sites/gcs_site.rb +++ b/lib/active_file/site/gcs_site.rb @@ -1,6 +1,6 @@ require "google/cloud/storage" -class ActiveFile::Sites::GCSSite < ActiveFile::Site +class ActiveFile::Site::GCSSite < ActiveFile::Site attr_reader :client, :bucket def initialize(project:, keyfile:, bucket:) diff --git a/lib/active_file/sites/mirror_site.rb b/lib/active_file/site/mirror_site.rb similarity index 92% rename from lib/active_file/sites/mirror_site.rb rename to lib/active_file/site/mirror_site.rb index 051d139af1..65f28cd437 100644 --- a/lib/active_file/sites/mirror_site.rb +++ b/lib/active_file/site/mirror_site.rb @@ -1,4 +1,4 @@ -class ActiveFile::Sites::MirrorSite < ActiveFile::Site +class ActiveFile::Site::MirrorSite < ActiveFile::Site attr_reader :sites def initialize(sites:) diff --git a/lib/active_file/sites/s3_site.rb b/lib/active_file/site/s3_site.rb similarity index 96% rename from lib/active_file/sites/s3_site.rb rename to lib/active_file/site/s3_site.rb index e13b609881..7bb8197245 100644 --- a/lib/active_file/sites/s3_site.rb +++ b/lib/active_file/site/s3_site.rb @@ -1,6 +1,6 @@ require "aws-sdk" -class ActiveFile::Sites::S3Site < ActiveFile::Site +class ActiveFile::Site::S3Site < ActiveFile::Site attr_reader :client, :bucket def initialize(access_key_id:, secret_access_key:, region:, bucket:) diff --git a/test/sites/.gitignore b/test/site/.gitignore similarity index 100% rename from test/sites/.gitignore rename to test/site/.gitignore diff --git a/test/sites/configurations-example.yml b/test/site/configurations-example.yml similarity index 100% rename from test/sites/configurations-example.yml rename to test/site/configurations-example.yml diff --git a/test/site/disk_site_test.rb b/test/site/disk_site_test.rb new file mode 100644 index 0000000000..63f12ad335 --- /dev/null +++ b/test/site/disk_site_test.rb @@ -0,0 +1,8 @@ +require "tmpdir" +require "site/shared_site_tests" + +class ActiveFile::Site::DiskSiteTest < ActiveSupport::TestCase + SITE = ActiveFile::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_file")) + + include ActiveFile::Site::SharedSiteTests +end diff --git a/test/site/gcs_site_test.rb b/test/site/gcs_site_test.rb new file mode 100644 index 0000000000..c5f32a0595 --- /dev/null +++ b/test/site/gcs_site_test.rb @@ -0,0 +1,11 @@ +require "site/shared_site_tests" + +if SITE_CONFIGURATIONS[:gcs] + class ActiveFile::Site::GCSSiteTest < ActiveSupport::TestCase + SITE = ActiveFile::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) + + include ActiveFile::Site::SharedSiteTests + end +else + puts "Skipping GCS Site tests because no GCS configuration was supplied" +end diff --git a/test/site/s3_site_test.rb b/test/site/s3_site_test.rb new file mode 100644 index 0000000000..7629b78ad5 --- /dev/null +++ b/test/site/s3_site_test.rb @@ -0,0 +1,11 @@ +require "site/shared_site_tests" + +if SITE_CONFIGURATIONS[:s3] + class ActiveFile::Site::S3SiteTest < ActiveSupport::TestCase + SITE = ActiveFile::Site.configure(:S3, SITE_CONFIGURATIONS[:s3]) + + include ActiveFile::Site::SharedSiteTests + end +else + puts "Skipping S3 Site tests because no S3 configuration was supplied" +end diff --git a/test/sites/shared_site_tests.rb b/test/site/shared_site_tests.rb similarity index 97% rename from test/sites/shared_site_tests.rb rename to test/site/shared_site_tests.rb index de28d7ae63..de1a54b874 100644 --- a/test/sites/shared_site_tests.rb +++ b/test/site/shared_site_tests.rb @@ -8,7 +8,7 @@ puts "Missing site configuration file in test/sites/configurations.yml" end -module ActiveFile::Sites::SharedSiteTests +module ActiveFile::Site::SharedSiteTests extend ActiveSupport::Concern FIXTURE_KEY = SecureRandom.base58(24) diff --git a/test/sites/disk_site_test.rb b/test/sites/disk_site_test.rb deleted file mode 100644 index 0956f08528..0000000000 --- a/test/sites/disk_site_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "tmpdir" -require "sites/shared_site_tests" - -class ActiveFile::Sites::DiskSiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Sites::DiskSite.new(root: File.join(Dir.tmpdir, "active_file")) - - include ActiveFile::Sites::SharedSiteTests -end diff --git a/test/sites/gcs_site_test.rb b/test/sites/gcs_site_test.rb deleted file mode 100644 index fbf6c4a242..0000000000 --- a/test/sites/gcs_site_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "sites/shared_site_tests" - -if SITE_CONFIGURATIONS[:gcs] - class ActiveFile::Sites::GCSSiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Sites::GCSSite.new(SITE_CONFIGURATIONS[:gcs]) - - include ActiveFile::Sites::SharedSiteTests - end -else - puts "Skipping GCS Site tests because no GCS configuration was supplied" -end diff --git a/test/sites/s3_site_test.rb b/test/sites/s3_site_test.rb deleted file mode 100644 index 12f5d084f6..0000000000 --- a/test/sites/s3_site_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "sites/shared_site_tests" - -if SITE_CONFIGURATIONS[:s3] - class ActiveFile::Sites::S3SiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Sites::S3Site.new(SITE_CONFIGURATIONS[:s3]) - - include ActiveFile::Sites::SharedSiteTests - end -else - puts "Skipping S3 Site tests because no S3 configuration was supplied" -end diff --git a/test/test_helper.rb b/test/test_helper.rb index 5be2631ceb..1f947fce90 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,7 +6,7 @@ require "active_file" require "active_file/site" -ActiveFile::Blob.site = ActiveFile::Sites::DiskSite.new(root: File.join(Dir.tmpdir, "active_file")) +ActiveFile::Blob.site = ActiveFile::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_file")) require "active_file/verified_key_with_expiration" ActiveFile::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") From 4dc60aabcc3785cff8cf9da9265f07b55843f8b0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 16:46:07 +0200 Subject: [PATCH 055/289] Disposition is a header, not part of a URL --- lib/active_file/blob.rb | 4 ++-- lib/active_file/site/disk_site.rb | 2 +- lib/active_file/site/s3_site.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 4af0551f99..bf34aac794 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -33,8 +33,8 @@ def filename ActiveFile::Filename.new(self[:filename]) end - def url(disposition: :inline, expires_in: 5.minutes) - site.url key, disposition: disposition, expires_in: expires_in + def url(expires_in: 5.minutes) + site.url key, expires_in: expires_in end diff --git a/lib/active_file/site/disk_site.rb b/lib/active_file/site/disk_site.rb index 1fa77029c7..f9aab475aa 100644 --- a/lib/active_file/site/disk_site.rb +++ b/lib/active_file/site/disk_site.rb @@ -38,7 +38,7 @@ def exist?(key) end - def url(key, disposition:, expires_in: nil) + def url(key, expires_in: nil) verified_key_with_expiration = ActiveFile::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) if defined?(Rails) diff --git a/lib/active_file/site/s3_site.rb b/lib/active_file/site/s3_site.rb index 7bb8197245..e407f84861 100644 --- a/lib/active_file/site/s3_site.rb +++ b/lib/active_file/site/s3_site.rb @@ -29,7 +29,7 @@ def exist?(key) end - def url(key, disposition: :inline, expires_in: nil) + def url(key, expires_in: nil) object_for(key).presigned_url(:get, expires_in: expires_in) end From 5dfbc5878d67824c6dc51b6e58c34b77c07cc3bc Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 17:00:36 +0200 Subject: [PATCH 056/289] Pair down interface to match what's been implemented --- lib/active_file/site.rb | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 31640695d5..260b84ab88 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -7,8 +7,6 @@ def self.configure(site, **options) end end - def initialize - end def upload(key, io) raise NotImplementedError @@ -26,20 +24,16 @@ def exist?(key) raise NotImplementedError end - def url(key) + + def url(key, expires_in: nil) + raise NotImplementedError + end + + def bytesize(key) raise NotImplementedError end def checksum(key) raise NotImplementedError end - - - def copy(from:, to:) - raise NotImplementedError - end - - def move(from:, to:) - raise NotImplementedError - end end From f5d663708bb1a4e56014ad8e3f5441803a5bd3a9 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 17:00:41 +0200 Subject: [PATCH 057/289] Breathing room --- lib/active_file/site/disk_site.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/active_file/site/disk_site.rb b/lib/active_file/site/disk_site.rb index f9aab475aa..3a98971274 100644 --- a/lib/active_file/site/disk_site.rb +++ b/lib/active_file/site/disk_site.rb @@ -8,7 +8,6 @@ def initialize(root:) @root = root end - def upload(key, io) File.open(make_path_for(key), "wb") do |file| while chunk = io.read(65536) From bbfc73ae5a84a11dc10642c37bcac1d7530a88e6 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 17:04:40 +0200 Subject: [PATCH 058/289] Test filename --- test/filename_test.rb | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/filename_test.rb diff --git a/test/filename_test.rb b/test/filename_test.rb new file mode 100644 index 0000000000..c42ae8ca54 --- /dev/null +++ b/test/filename_test.rb @@ -0,0 +1,36 @@ +require "test_helper" + +class ActiveFile::FilenameTest < ActiveSupport::TestCase + test "sanitize" do + "%$|:;/\t\r\n\\".each_char do |character| + filename = ActiveFile::Filename.new("foo#{character}bar.pdf") + assert_equal 'foo-bar.pdf', filename.sanitized + assert_equal 'foo-bar.pdf', filename.to_s + end + end + + test "sanitize transcodes to valid UTF-8" do + { "\xF6".force_encoding(Encoding::ISO8859_1) => "ö", + "\xC3".force_encoding(Encoding::ISO8859_1) => "Ã", + "\xAD" => "�", + "\xCF" => "�", + "\x00" => "", + }.each do |actual, expected| + assert_equal expected, ActiveFile::Filename.new(actual).sanitized + end + end + + test "strips RTL override chars used to spoof unsafe executables as docs" do + # Would be displayed in Windows as "evilexe.pdf" due to the right-to-left + # (RTL) override char! + assert_equal 'evil-fdp.exe', ActiveFile::Filename.new("evil\u{202E}fdp.exe").sanitized + end + + test "compare case-insensitively" do + assert_operator ActiveFile::Filename.new('foobar.pdf'), :==, ActiveFile::Filename.new('FooBar.PDF') + end + + test "compare sanitized" do + assert_operator ActiveFile::Filename.new('foo-bar.pdf'), :==, ActiveFile::Filename.new("foo\tbar.pdf") + end +end From 9b9d69b34ea77ee62bd0ef1846767db9f795c301 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 17:29:30 +0200 Subject: [PATCH 059/289] Rescue require exception --- lib/active_file/site.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 260b84ab88..fc098e694f 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -4,6 +4,8 @@ def self.configure(site, **options) begin require "active_file/site/#{site.to_s.downcase}_site" ActiveFile::Site.const_get(:"#{site}Site").new(**options) + rescue LoadError + puts "Couldn't configure unknow site: #{site}" end end From 7409bb2ff8e20f9036842a68f34e215cefdd98d9 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 17:34:37 +0200 Subject: [PATCH 060/289] Actually #url needs to deal with the disposition --- lib/active_file/blob.rb | 4 ++-- lib/active_file/site.rb | 2 +- lib/active_file/site/disk_site.rb | 6 +++--- lib/active_file/site/s3_site.rb | 5 +++-- test/blob_test.rb | 10 ++++++++-- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index bf34aac794..8a1950c9dc 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -33,8 +33,8 @@ def filename ActiveFile::Filename.new(self[:filename]) end - def url(expires_in: 5.minutes) - site.url key, expires_in: expires_in + def url(expires_in: 5.minutes, disposition: :inline) + site.url key, expires_in: expires_in, disposition: disposition, filename: filename end diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index fc098e694f..1c71f74f0d 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -27,7 +27,7 @@ def exist?(key) end - def url(key, expires_in: nil) + def url(key, expires_in:, disposition:, filename:) raise NotImplementedError end diff --git a/lib/active_file/site/disk_site.rb b/lib/active_file/site/disk_site.rb index 3a98971274..ec60175bbf 100644 --- a/lib/active_file/site/disk_site.rb +++ b/lib/active_file/site/disk_site.rb @@ -37,13 +37,13 @@ def exist?(key) end - def url(key, expires_in: nil) + def url(key, expires_in:, disposition:, filename:) verified_key_with_expiration = ActiveFile::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) if defined?(Rails) - Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration) + Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition) else - "/rails/blobs/#{verified_key_with_expiration}" + "/rails/blobs/#{verified_key_with_expiration}?disposition=#{disposition}" end end diff --git a/lib/active_file/site/s3_site.rb b/lib/active_file/site/s3_site.rb index e407f84861..cfd2ddcc9a 100644 --- a/lib/active_file/site/s3_site.rb +++ b/lib/active_file/site/s3_site.rb @@ -29,8 +29,9 @@ def exist?(key) end - def url(key, expires_in: nil) - object_for(key).presigned_url(:get, expires_in: expires_in) + def url(key, expires_in:, disposition:, filename:) + object_for(key).presigned_url :get, expires_in: expires_in, + response_content_disposition: "#{disposition}; filename=#{filename}" end def byte_size(key) diff --git a/test/blob_test.rb b/test/blob_test.rb index c726555dc6..45f6b5e3ba 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -12,16 +12,22 @@ class ActiveFile::BlobTest < ActiveSupport::TestCase assert_equal Digest::MD5.hexdigest(data), blob.checksum end - test "url expiring in 5 minutes" do + test "urls expiring in 5 minutes" do blob = create_blob travel_to Time.now do - assert_equal "/rails/blobs/#{ActiveFile::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}", blob.url + assert_equal expected_url_for(blob), blob.url + assert_equal expected_url_for(blob, disposition: :attachment), blob.url(disposition: :attachment) end end + private def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") ActiveFile::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type end + + def expected_url_for(blob, disposition: :inline) + "/rails/blobs/#{ActiveFile::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}?disposition=#{disposition}" + end end From 54fe33cc30f8a113ffc3dc7306adb831ed97bdc6 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 17:36:29 +0200 Subject: [PATCH 061/289] Use explaining parameter name --- lib/active_file/disk_controller.rb | 2 +- lib/active_file/railtie.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_file/disk_controller.rb b/lib/active_file/disk_controller.rb index f016c90fc5..5140c91f0a 100644 --- a/lib/active_file/disk_controller.rb +++ b/lib/active_file/disk_controller.rb @@ -10,7 +10,7 @@ def show private def decode_verified_key - ActiveFile::Site::DiskSite::VerifiedKeyWithExpiration.decode(params[:id]) + ActiveFile::Site::DiskSite::VerifiedKeyWithExpiration.decode(params[:encoded_key]) end def disposition_param diff --git a/lib/active_file/railtie.rb b/lib/active_file/railtie.rb index 4398bb6072..6a22148040 100644 --- a/lib/active_file/railtie.rb +++ b/lib/active_file/railtie.rb @@ -11,7 +11,7 @@ class Railtie < Rails::Railtie # :nodoc: config.after_initialize do |app| app.routes.prepend do - get "/rails/blobs/:id" => "active_file/disk#show", as: :rails_disk_blob + get "/rails/blobs/:encoded_key" => "active_file/disk#show", as: :rails_disk_blob end end end From a39295d85b5f0cb326069af163c71651b0ca5b3e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 17:43:33 +0200 Subject: [PATCH 062/289] Fix copy-pasta references --- lib/active_file/railtie.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_file/railtie.rb b/lib/active_file/railtie.rb index 6a22148040..18e4779229 100644 --- a/lib/active_file/railtie.rb +++ b/lib/active_file/railtie.rb @@ -2,11 +2,11 @@ module ActiveFile class Railtie < Rails::Railtie # :nodoc: - config.action_cable = ActiveSupport::OrderedOptions.new + config.action_file = ActiveSupport::OrderedOptions.new config.eager_load_namespaces << ActiveFile - initializer "action_cable.routes" do + initializer "action_file.routes" do require "active_file/disk_controller" config.after_initialize do |app| From 09878fb19d46674605fb6e48c1b86e2915ad7496 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 18:10:53 +0200 Subject: [PATCH 063/289] Extract create_blob test helper --- test/blob_test.rb | 5 ----- test/test_helper.rb | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/blob_test.rb b/test/blob_test.rb index 45f6b5e3ba..9d190fb703 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -21,12 +21,7 @@ class ActiveFile::BlobTest < ActiveSupport::TestCase end end - private - def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") - ActiveFile::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type - end - def expected_url_for(blob, disposition: :inline) "/rails/blobs/#{ActiveFile::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}?disposition=#{disposition}" end diff --git a/test/test_helper.rb b/test/test_helper.rb index 1f947fce90..9bb4a2fca1 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,6 @@ require "bundler/setup" require "active_support" +require "active_support/test_case" require "active_support/testing/autorun" require "byebug" @@ -10,3 +11,10 @@ require "active_file/verified_key_with_expiration" ActiveFile::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") + +class ActiveSupport::TestCase + private + def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") + ActiveFile::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type + end +end \ No newline at end of file From 4712e2361199c0becc216d392d3d84666bad076b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 18:11:06 +0200 Subject: [PATCH 064/289] Fix up DiskController and add basic testing --- Gemfile.lock | 29 +++++++++++++++++++++++++ activefile.gemspec | 1 + lib/active_file/disk_controller.rb | 13 ++++++++++-- lib/active_file/site/disk_site.rb | 2 +- test/disk_controller_test.rb | 34 ++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 test/disk_controller_test.rb diff --git a/Gemfile.lock b/Gemfile.lock index 7078d544cb..a45fa10292 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,7 @@ PATH remote: . specs: activefile (0.1) + actionpack (>= 5.1) activejob (>= 5.1) activerecord (>= 5.1) activesupport (>= 5.1) @@ -9,6 +10,19 @@ PATH GEM remote: https://rubygems.org/ specs: + actionpack (5.1.1) + actionview (= 5.1.1) + activesupport (= 5.1.1) + rack (~> 2.0) + rack-test (~> 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.1.1) + activesupport (= 5.1.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) activejob (5.1.1) activesupport (= 5.1.1) globalid (>= 0.3.6) @@ -34,11 +48,13 @@ GEM aws-sdk-resources (2.10.7) aws-sdk-core (= 2.10.7) aws-sigv4 (1.0.0) + builder (3.2.3) byebug (9.0.6) concurrent-ruby (1.0.5) declarative (0.0.9) declarative-option (0.1.0) digest-crc (0.4.1) + erubi (1.6.0) faraday (0.12.1) multipart-post (>= 1.2, < 3) globalid (0.4.0) @@ -161,15 +177,28 @@ GEM logging (2.2.2) little-plugger (~> 1.1) multi_json (~> 1.10) + loofah (2.0.3) + nokogiri (>= 1.5.9) memoist (0.16.0) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) + mini_portile2 (2.1.0) minitest (5.10.2) multi_json (1.12.1) multipart-post (2.0.0) + nokogiri (1.7.2) + mini_portile2 (~> 2.1.0) os (0.9.6) public_suffix (2.0.5) + rack (2.0.3) + rack-test (0.6.3) + rack (>= 1.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) rake (12.0.0) representable (3.0.4) declarative (< 0.1.0) diff --git a/activefile.gemspec b/activefile.gemspec index e05421102d..cc33808780 100644 --- a/activefile.gemspec +++ b/activefile.gemspec @@ -11,6 +11,7 @@ s.add_dependency "activesupport", ">= 5.1" s.add_dependency "activerecord", ">= 5.1" + s.add_dependency "actionpack", ">= 5.1" s.add_dependency "activejob", ">= 5.1" s.add_development_dependency "bundler", "~> 1.15" diff --git a/lib/active_file/disk_controller.rb b/lib/active_file/disk_controller.rb index 5140c91f0a..d778cf066f 100644 --- a/lib/active_file/disk_controller.rb +++ b/lib/active_file/disk_controller.rb @@ -1,8 +1,17 @@ +require "action_controller" +require "active_file/blob" +require "active_file/verified_key_with_expiration" + +require "active_support/core_ext/object/inclusion" + class ActiveFile::DiskController < ActionController::Base def show if key = decode_verified_key blob = ActiveFile::Blob.find_by!(key: key) - send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param + + if stale?(etag: blob.checksum) + send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param + end else head :not_found end @@ -10,7 +19,7 @@ def show private def decode_verified_key - ActiveFile::Site::DiskSite::VerifiedKeyWithExpiration.decode(params[:encoded_key]) + ActiveFile::VerifiedKeyWithExpiration.decode(params[:encoded_key]) end def disposition_param diff --git a/lib/active_file/site/disk_site.rb b/lib/active_file/site/disk_site.rb index ec60175bbf..7916a642c0 100644 --- a/lib/active_file/site/disk_site.rb +++ b/lib/active_file/site/disk_site.rb @@ -40,7 +40,7 @@ def exist?(key) def url(key, expires_in:, disposition:, filename:) verified_key_with_expiration = ActiveFile::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) - if defined?(Rails) + if defined?(Rails) && defined?(Rails.application) Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition) else "/rails/blobs/#{verified_key_with_expiration}?disposition=#{disposition}" diff --git a/test/disk_controller_test.rb b/test/disk_controller_test.rb new file mode 100644 index 0000000000..ee172b23f7 --- /dev/null +++ b/test/disk_controller_test.rb @@ -0,0 +1,34 @@ +require "test_helper" +require "database/setup" + +require "action_controller" +require "action_controller/test_case" + +require "active_file/disk_controller" +require "active_file/verified_key_with_expiration" + +class ActiveFile::DiskControllerTest < ActionController::TestCase + Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| + routes.draw do + get "/rails/blobs/:encoded_key" => "active_file/disk#show", as: :rails_disk_blob + end + end + + setup do + @blob = create_blob + @routes = Routes + @controller = ActiveFile::DiskController.new + end + + test "showing blob inline" do + get :show, params: { encoded_key: ActiveFile::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes) } + assert_equal "inline; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] + assert_equal "text/plain", @response.headers["Content-Type"] + end + + test "sending blob as attachment" do + get :show, params: { encoded_key: ActiveFile::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), disposition: :attachment } + assert_equal "attachment; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] + assert_equal "text/plain", @response.headers["Content-Type"] + end +end From 44b8ac48e36672a0b63b225ec19eacc8d64e7e09 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 18:43:56 +0200 Subject: [PATCH 065/289] Fix filename reference --- lib/active_file/blob.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 8a1950c9dc..74fadf5109 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -13,8 +13,8 @@ class ActiveFile::Blob < ActiveRecord::Base class << self def build_after_upload(io:, filename:, content_type: nil, metadata: nil) new.tap do |blob| - blob.filename = name blob.content_type = content_type # Marcel::MimeType.for(data, name: name, declared_type: content_type) + blob.filename = filename blob.upload io end end From 8dc2542721ed5c0c2f5f44ff408306c7328adce7 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 18:44:06 +0200 Subject: [PATCH 066/289] Wait on Marcel for now --- lib/active_file/blob.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/active_file/blob.rb b/lib/active_file/blob.rb index 74fadf5109..d8b9cd07d2 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_file/blob.rb @@ -13,8 +13,9 @@ class ActiveFile::Blob < ActiveRecord::Base class << self def build_after_upload(io:, filename:, content_type: nil, metadata: nil) new.tap do |blob| - blob.content_type = content_type # Marcel::MimeType.for(data, name: name, declared_type: content_type) blob.filename = filename + blob.content_type = content_type + blob.upload io end end From 9201d73865ae19850b92442dbd8a629e9891e868 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 18:52:11 +0200 Subject: [PATCH 067/289] Better error reporting --- lib/active_file/site.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_file/site.rb b/lib/active_file/site.rb index 1c71f74f0d..19cbbc754e 100644 --- a/lib/active_file/site.rb +++ b/lib/active_file/site.rb @@ -4,8 +4,8 @@ def self.configure(site, **options) begin require "active_file/site/#{site.to_s.downcase}_site" ActiveFile::Site.const_get(:"#{site}Site").new(**options) - rescue LoadError - puts "Couldn't configure unknow site: #{site}" + rescue LoadError => e + puts "Couldn't configure site: #{site} (#{e.message})" end end From e635dac88f0dfcc36a2313c10f860cb6e3a52cfa Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 4 Jul 2017 18:52:26 +0200 Subject: [PATCH 068/289] Quote the filename to deal with spaces --- lib/active_file/site/s3_site.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_file/site/s3_site.rb b/lib/active_file/site/s3_site.rb index cfd2ddcc9a..25a876c697 100644 --- a/lib/active_file/site/s3_site.rb +++ b/lib/active_file/site/s3_site.rb @@ -31,7 +31,7 @@ def exist?(key) def url(key, expires_in:, disposition:, filename:) object_for(key).presigned_url :get, expires_in: expires_in, - response_content_disposition: "#{disposition}; filename=#{filename}" + response_content_disposition: "#{disposition}; filename=\"#{filename}\"" end def byte_size(key) From 5159d030fad632ac544555541ab1c85ac132874e Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Tue, 4 Jul 2017 14:01:03 -0400 Subject: [PATCH 069/289] ActiveFile::Site::GCSSite#url --- lib/active_file/site/gcs_site.rb | 5 +++++ test/site/gcs_site_test.rb | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/active_file/site/gcs_site.rb b/lib/active_file/site/gcs_site.rb index c5f3d634cf..001339f2bd 100644 --- a/lib/active_file/site/gcs_site.rb +++ b/lib/active_file/site/gcs_site.rb @@ -27,6 +27,11 @@ def exist?(key) end + def url(key, expires_in:, disposition:, filename:) + file_for(key).signed_url(expires: expires_in) + "&" + + { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"" }.to_query + end + def byte_size(key) file_for(key).size end diff --git a/test/site/gcs_site_test.rb b/test/site/gcs_site_test.rb index c5f32a0595..f6848b2fe9 100644 --- a/test/site/gcs_site_test.rb +++ b/test/site/gcs_site_test.rb @@ -2,9 +2,17 @@ if SITE_CONFIGURATIONS[:gcs] class ActiveFile::Site::GCSSiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) + SITE = ActiveFile::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) + SIGNER = Google::Cloud::Storage::File::Signer.from_bucket(SITE.bucket, ActiveFile::Site::SharedSiteTests::FIXTURE_KEY) include ActiveFile::Site::SharedSiteTests + + test "signed URL generation" do + travel_to Time.now do + assert_equal SIGNER.signed_url(expires: 120) + "&response-content-disposition=inline%3B+filename%3D%22test.txt%22", + @site.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") + end + end end else puts "Skipping GCS Site tests because no GCS configuration was supplied" From 4f6410795c053930d31ee651cf03b0efa6b38e61 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Tue, 4 Jul 2017 14:12:20 -0400 Subject: [PATCH 070/289] Eliminate SIGNER --- test/site/gcs_site_test.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/site/gcs_site_test.rb b/test/site/gcs_site_test.rb index f6848b2fe9..96591c9a8b 100644 --- a/test/site/gcs_site_test.rb +++ b/test/site/gcs_site_test.rb @@ -2,15 +2,16 @@ if SITE_CONFIGURATIONS[:gcs] class ActiveFile::Site::GCSSiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) - SIGNER = Google::Cloud::Storage::File::Signer.from_bucket(SITE.bucket, ActiveFile::Site::SharedSiteTests::FIXTURE_KEY) + SITE = ActiveFile::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) include ActiveFile::Site::SharedSiteTests test "signed URL generation" do travel_to Time.now do - assert_equal SIGNER.signed_url(expires: 120) + "&response-content-disposition=inline%3B+filename%3D%22test.txt%22", - @site.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") + url = SITE.bucket.signed_url(path: FIXTURE_KEY, expires: 120) + + "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" + + assert_equal url, @site.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") end end end From 571509ad12bf3bcb3190efd7494a38c4796302b8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 13:06:29 +0200 Subject: [PATCH 071/289] Rename from ActiveFile to ActiveVault since activefile gem name was taken --- Gemfile.lock | 4 ++-- README.md | 8 ++++---- activefile.gemspec => activevault.gemspec | 4 ++-- lib/active_file/purge_job.rb | 7 ------- lib/{active_file.rb => active_vault.rb} | 4 ++-- lib/{active_file => active_vault}/blob.rb | 10 +++++----- lib/{active_file => active_vault}/config/sites.yml | 6 +++--- .../disk_controller.rb | 10 +++++----- lib/{active_file => active_vault}/download.rb | 2 +- lib/{active_file => active_vault}/filename.rb | 2 +- lib/{active_file => active_vault}/migration.rb | 2 +- lib/active_vault/purge_job.rb | 7 +++++++ lib/{active_file => active_vault}/railtie.rb | 8 ++++---- lib/{active_file => active_vault}/site.rb | 6 +++--- .../site/disk_site.rb | 4 ++-- lib/{active_file => active_vault}/site/gcs_site.rb | 2 +- .../site/mirror_site.rb | 2 +- lib/{active_file => active_vault}/site/s3_site.rb | 2 +- .../verified_key_with_expiration.rb | 4 ++-- test/blob_test.rb | 6 +++--- test/database/setup.rb | 4 ++-- test/disk_controller_test.rb | 14 +++++++------- test/filename_test.rb | 12 ++++++------ test/site/disk_site_test.rb | 6 +++--- test/site/gcs_site_test.rb | 6 +++--- test/site/s3_site_test.rb | 6 +++--- test/site/shared_site_tests.rb | 2 +- test/test_helper.rb | 12 ++++++------ test/verified_key_with_expiration_test.rb | 12 ++++++------ 29 files changed, 87 insertions(+), 87 deletions(-) rename activefile.gemspec => activevault.gemspec (87%) delete mode 100644 lib/active_file/purge_job.rb rename lib/{active_file.rb => active_vault.rb} (58%) rename lib/{active_file => active_vault}/blob.rb (86%) rename lib/{active_file => active_vault}/config/sites.yml (79%) rename lib/{active_file => active_vault}/disk_controller.rb (65%) rename lib/{active_file => active_vault}/download.rb (99%) rename lib/{active_file => active_vault}/filename.rb (94%) rename lib/{active_file => active_vault}/migration.rb (81%) create mode 100644 lib/active_vault/purge_job.rb rename lib/{active_file => active_vault}/railtie.rb (57%) rename lib/{active_file => active_vault}/site.rb (81%) rename lib/{active_file => active_vault}/site/disk_site.rb (89%) rename lib/{active_file => active_vault}/site/gcs_site.rb (93%) rename lib/{active_file => active_vault}/site/mirror_site.rb (92%) rename lib/{active_file => active_vault}/site/s3_site.rb (96%) rename lib/{active_file => active_vault}/verified_key_with_expiration.rb (85%) diff --git a/Gemfile.lock b/Gemfile.lock index a45fa10292..e20ba22218 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - activefile (0.1) + activevault (0.1) actionpack (>= 5.1) activejob (>= 5.1) activerecord (>= 5.1) @@ -223,7 +223,7 @@ PLATFORMS ruby DEPENDENCIES - activefile! + activevault! aws-sdk bundler (~> 1.15) byebug diff --git a/README.md b/README.md index 7a59bcd18d..7b4c7f2aaa 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ end class Avatar < ApplicationRecord belongs_to :person - belongs_to :image, class_name: 'ActiveFile::Blob' + belongs_to :image, class_name: 'ActiveVault::Blob' has_file :image end @@ -19,9 +19,9 @@ end avatar.image.url(expires_in: 5.minutes) -class ActiveFile::DownloadsController < ActionController::Base +class ActiveVault::DownloadsController < ActionController::Base def show - head :ok, ActiveFile::Blob.locate(params[:id]).download_headers + head :ok, ActiveVault::Blob.locate(params[:id]).download_headers end end @@ -29,7 +29,7 @@ end class AvatarsController < ApplicationController def create # @avatar = Avatar.create \ - # image: ActiveFile::Blob.save!(file_name: params.require(:name), content_type: request.content_type, data: request.body) + # image: ActiveVault::Blob.save!(file_name: params.require(:name), content_type: request.content_type, data: request.body) @avatar = Avatar.create! image: Avatar.image.extract_from(request) end end diff --git a/activefile.gemspec b/activevault.gemspec similarity index 87% rename from activefile.gemspec rename to activevault.gemspec index cc33808780..7144563d18 100644 --- a/activefile.gemspec +++ b/activevault.gemspec @@ -1,10 +1,10 @@ Gem::Specification.new do |s| - s.name = "activefile" + s.name = "activevault" s.version = "0.1" s.authors = "David Heinemeier Hansson" s.email = "david@basecamp.com" s.summary = "Store files in Rails applications" - s.homepage = "https://github.com/rails/activefile" + s.homepage = "https://github.com/rails/activevault" s.license = "MIT" s.required_ruby_version = ">= 1.9.3" diff --git a/lib/active_file/purge_job.rb b/lib/active_file/purge_job.rb deleted file mode 100644 index 1a967db2f0..0000000000 --- a/lib/active_file/purge_job.rb +++ /dev/null @@ -1,7 +0,0 @@ -class ActiveFile::PurgeJob < ActiveJob::Base - retry_on ActiveFile::StorageException - - def perform(blob) - blob.purge - end -end diff --git a/lib/active_file.rb b/lib/active_vault.rb similarity index 58% rename from lib/active_file.rb rename to lib/active_vault.rb index b4b319fc8e..f47b09b4cd 100644 --- a/lib/active_file.rb +++ b/lib/active_vault.rb @@ -1,7 +1,7 @@ require "active_record" -require "active_file/railtie" if defined?(Rails) +require "active_vault/railtie" if defined?(Rails) -module ActiveFile +module ActiveVault extend ActiveSupport::Autoload autoload :Blob diff --git a/lib/active_file/blob.rb b/lib/active_vault/blob.rb similarity index 86% rename from lib/active_file/blob.rb rename to lib/active_vault/blob.rb index d8b9cd07d2..4948d43ec7 100644 --- a/lib/active_file/blob.rb +++ b/lib/active_vault/blob.rb @@ -1,8 +1,8 @@ -require "active_file/site" -require "active_file/filename" +require "active_vault/site" +require "active_vault/filename" # Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at -class ActiveFile::Blob < ActiveRecord::Base +class ActiveVault::Blob < ActiveRecord::Base self.table_name = "rails_blobs" has_secure_token :key @@ -31,7 +31,7 @@ def key end def filename - ActiveFile::Filename.new(self[:filename]) + ActiveVault::Filename.new(self[:filename]) end def url(expires_in: 5.minutes, disposition: :inline) @@ -61,6 +61,6 @@ def purge end def purge_later - ActiveFile::PurgeJob.perform_later(self) + ActiveVault::PurgeJob.perform_later(self) end end diff --git a/lib/active_file/config/sites.yml b/lib/active_vault/config/sites.yml similarity index 79% rename from lib/active_file/config/sites.yml rename to lib/active_vault/config/sites.yml index bb550aed7a..334e779b28 100644 --- a/lib/active_file/config/sites.yml +++ b/lib/active_vault/config/sites.yml @@ -1,13 +1,13 @@ # Configuration should be something like this: # # config/environments/development.rb -# config.active_file.site = :local +# config.active_vault.site = :local # # config/environments/production.rb -# config.active_file.site = :amazon +# config.active_vault.site = :amazon local: site: Disk - root: <%%= File.join(Dir.tmpdir, "active_file") %> + root: <%%= File.join(Dir.tmpdir, "active_vault") %> amazon: site: S3 diff --git a/lib/active_file/disk_controller.rb b/lib/active_vault/disk_controller.rb similarity index 65% rename from lib/active_file/disk_controller.rb rename to lib/active_vault/disk_controller.rb index d778cf066f..623569f0f6 100644 --- a/lib/active_file/disk_controller.rb +++ b/lib/active_vault/disk_controller.rb @@ -1,13 +1,13 @@ require "action_controller" -require "active_file/blob" -require "active_file/verified_key_with_expiration" +require "active_vault/blob" +require "active_vault/verified_key_with_expiration" require "active_support/core_ext/object/inclusion" -class ActiveFile::DiskController < ActionController::Base +class ActiveVault::DiskController < ActionController::Base def show if key = decode_verified_key - blob = ActiveFile::Blob.find_by!(key: key) + blob = ActiveVault::Blob.find_by!(key: key) if stale?(etag: blob.checksum) send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param @@ -19,7 +19,7 @@ def show private def decode_verified_key - ActiveFile::VerifiedKeyWithExpiration.decode(params[:encoded_key]) + ActiveVault::VerifiedKeyWithExpiration.decode(params[:encoded_key]) end def disposition_param diff --git a/lib/active_file/download.rb b/lib/active_vault/download.rb similarity index 99% rename from lib/active_file/download.rb rename to lib/active_vault/download.rb index 74f69a9dfc..6e74056062 100644 --- a/lib/active_file/download.rb +++ b/lib/active_vault/download.rb @@ -1,4 +1,4 @@ -class ActiveFile::Download +class ActiveVault::Download # Sending .ai files as application/postscript to Safari opens them in a blank, grey screen. # Downloading .ai as application/postscript files in Safari appends .ps to the extension. # Sending HTML, SVG, XML and SWF files as binary closes XSS vulnerabilities. diff --git a/lib/active_file/filename.rb b/lib/active_vault/filename.rb similarity index 94% rename from lib/active_file/filename.rb rename to lib/active_vault/filename.rb index b3c184e26c..647d037b1f 100644 --- a/lib/active_file/filename.rb +++ b/lib/active_vault/filename.rb @@ -1,4 +1,4 @@ -class ActiveFile::Filename +class ActiveVault::Filename include Comparable def initialize(filename) diff --git a/lib/active_file/migration.rb b/lib/active_vault/migration.rb similarity index 81% rename from lib/active_file/migration.rb rename to lib/active_vault/migration.rb index 1c87444dd4..cc7a535f39 100644 --- a/lib/active_file/migration.rb +++ b/lib/active_vault/migration.rb @@ -1,4 +1,4 @@ -class ActiveFile::CreateBlobs < ActiveRecord::Migration[5.1] +class ActiveVault::CreateBlobs < ActiveRecord::Migration[5.1] def change create_table :rails_blobs do |t| t.string :key diff --git a/lib/active_vault/purge_job.rb b/lib/active_vault/purge_job.rb new file mode 100644 index 0000000000..d7634af2bb --- /dev/null +++ b/lib/active_vault/purge_job.rb @@ -0,0 +1,7 @@ +class ActiveVault::PurgeJob < ActiveJob::Base + retry_on ActiveVault::StorageException + + def perform(blob) + blob.purge + end +end diff --git a/lib/active_file/railtie.rb b/lib/active_vault/railtie.rb similarity index 57% rename from lib/active_file/railtie.rb rename to lib/active_vault/railtie.rb index 18e4779229..c254f4c77c 100644 --- a/lib/active_file/railtie.rb +++ b/lib/active_vault/railtie.rb @@ -1,17 +1,17 @@ require "rails/railtie" -module ActiveFile +module ActiveVault class Railtie < Rails::Railtie # :nodoc: config.action_file = ActiveSupport::OrderedOptions.new - config.eager_load_namespaces << ActiveFile + config.eager_load_namespaces << ActiveVault initializer "action_file.routes" do - require "active_file/disk_controller" + require "active_vault/disk_controller" config.after_initialize do |app| app.routes.prepend do - get "/rails/blobs/:encoded_key" => "active_file/disk#show", as: :rails_disk_blob + get "/rails/blobs/:encoded_key" => "active_vault/disk#show", as: :rails_disk_blob end end end diff --git a/lib/active_file/site.rb b/lib/active_vault/site.rb similarity index 81% rename from lib/active_file/site.rb rename to lib/active_vault/site.rb index 19cbbc754e..29eddf1566 100644 --- a/lib/active_file/site.rb +++ b/lib/active_vault/site.rb @@ -1,9 +1,9 @@ # Abstract class serving as an interface for concrete sites. -class ActiveFile::Site +class ActiveVault::Site def self.configure(site, **options) begin - require "active_file/site/#{site.to_s.downcase}_site" - ActiveFile::Site.const_get(:"#{site}Site").new(**options) + require "active_vault/site/#{site.to_s.downcase}_site" + ActiveVault::Site.const_get(:"#{site}Site").new(**options) rescue LoadError => e puts "Couldn't configure site: #{site} (#{e.message})" end diff --git a/lib/active_file/site/disk_site.rb b/lib/active_vault/site/disk_site.rb similarity index 89% rename from lib/active_file/site/disk_site.rb rename to lib/active_vault/site/disk_site.rb index 7916a642c0..73f86bac6a 100644 --- a/lib/active_file/site/disk_site.rb +++ b/lib/active_vault/site/disk_site.rb @@ -1,7 +1,7 @@ require "fileutils" require "pathname" -class ActiveFile::Site::DiskSite < ActiveFile::Site +class ActiveVault::Site::DiskSite < ActiveVault::Site attr_reader :root def initialize(root:) @@ -38,7 +38,7 @@ def exist?(key) def url(key, expires_in:, disposition:, filename:) - verified_key_with_expiration = ActiveFile::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) + verified_key_with_expiration = ActiveVault::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) if defined?(Rails) && defined?(Rails.application) Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition) diff --git a/lib/active_file/site/gcs_site.rb b/lib/active_vault/site/gcs_site.rb similarity index 93% rename from lib/active_file/site/gcs_site.rb rename to lib/active_vault/site/gcs_site.rb index c5f3d634cf..e509ebbbd2 100644 --- a/lib/active_file/site/gcs_site.rb +++ b/lib/active_vault/site/gcs_site.rb @@ -1,6 +1,6 @@ require "google/cloud/storage" -class ActiveFile::Site::GCSSite < ActiveFile::Site +class ActiveVault::Site::GCSSite < ActiveVault::Site attr_reader :client, :bucket def initialize(project:, keyfile:, bucket:) diff --git a/lib/active_file/site/mirror_site.rb b/lib/active_vault/site/mirror_site.rb similarity index 92% rename from lib/active_file/site/mirror_site.rb rename to lib/active_vault/site/mirror_site.rb index 65f28cd437..67d79a2607 100644 --- a/lib/active_file/site/mirror_site.rb +++ b/lib/active_vault/site/mirror_site.rb @@ -1,4 +1,4 @@ -class ActiveFile::Site::MirrorSite < ActiveFile::Site +class ActiveVault::Site::MirrorSite < ActiveVault::Site attr_reader :sites def initialize(sites:) diff --git a/lib/active_file/site/s3_site.rb b/lib/active_vault/site/s3_site.rb similarity index 96% rename from lib/active_file/site/s3_site.rb rename to lib/active_vault/site/s3_site.rb index 25a876c697..49a7522170 100644 --- a/lib/active_file/site/s3_site.rb +++ b/lib/active_vault/site/s3_site.rb @@ -1,6 +1,6 @@ require "aws-sdk" -class ActiveFile::Site::S3Site < ActiveFile::Site +class ActiveVault::Site::S3Site < ActiveVault::Site attr_reader :client, :bucket def initialize(access_key_id:, secret_access_key:, region:, bucket:) diff --git a/lib/active_file/verified_key_with_expiration.rb b/lib/active_vault/verified_key_with_expiration.rb similarity index 85% rename from lib/active_file/verified_key_with_expiration.rb rename to lib/active_vault/verified_key_with_expiration.rb index e9e811d364..95d4993ff0 100644 --- a/lib/active_file/verified_key_with_expiration.rb +++ b/lib/active_vault/verified_key_with_expiration.rb @@ -1,5 +1,5 @@ -class ActiveFile::VerifiedKeyWithExpiration - class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveFile') : nil +class ActiveVault::VerifiedKeyWithExpiration + class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveVault') : nil class << self def encode(key, expires_in: nil) diff --git a/test/blob_test.rb b/test/blob_test.rb index 9d190fb703..c7b4aeed39 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -1,8 +1,8 @@ require "test_helper" require "database/setup" -require "active_file/blob" +require "active_vault/blob" -class ActiveFile::BlobTest < ActiveSupport::TestCase +class ActiveVault::BlobTest < ActiveSupport::TestCase test "create after upload sets byte size and checksum" do data = "Hello world!" blob = create_blob data: data @@ -23,6 +23,6 @@ class ActiveFile::BlobTest < ActiveSupport::TestCase private def expected_url_for(blob, disposition: :inline) - "/rails/blobs/#{ActiveFile::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}?disposition=#{disposition}" + "/rails/blobs/#{ActiveVault::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}?disposition=#{disposition}" end end diff --git a/test/database/setup.rb b/test/database/setup.rb index 21ede8f49c..bc6e8b9ec1 100644 --- a/test/database/setup.rb +++ b/test/database/setup.rb @@ -1,4 +1,4 @@ -require "active_file/migration" +require "active_vault/migration" ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') -ActiveFile::CreateBlobs.migrate(:up) +ActiveVault::CreateBlobs.migrate(:up) diff --git a/test/disk_controller_test.rb b/test/disk_controller_test.rb index ee172b23f7..eaf0b497ac 100644 --- a/test/disk_controller_test.rb +++ b/test/disk_controller_test.rb @@ -4,30 +4,30 @@ require "action_controller" require "action_controller/test_case" -require "active_file/disk_controller" -require "active_file/verified_key_with_expiration" +require "active_vault/disk_controller" +require "active_vault/verified_key_with_expiration" -class ActiveFile::DiskControllerTest < ActionController::TestCase +class ActiveVault::DiskControllerTest < ActionController::TestCase Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| routes.draw do - get "/rails/blobs/:encoded_key" => "active_file/disk#show", as: :rails_disk_blob + get "/rails/blobs/:encoded_key" => "active_vault/disk#show", as: :rails_disk_blob end end setup do @blob = create_blob @routes = Routes - @controller = ActiveFile::DiskController.new + @controller = ActiveVault::DiskController.new end test "showing blob inline" do - get :show, params: { encoded_key: ActiveFile::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes) } + get :show, params: { encoded_key: ActiveVault::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes) } assert_equal "inline; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end test "sending blob as attachment" do - get :show, params: { encoded_key: ActiveFile::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), disposition: :attachment } + get :show, params: { encoded_key: ActiveVault::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), disposition: :attachment } assert_equal "attachment; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end diff --git a/test/filename_test.rb b/test/filename_test.rb index c42ae8ca54..5cb67016c0 100644 --- a/test/filename_test.rb +++ b/test/filename_test.rb @@ -1,9 +1,9 @@ require "test_helper" -class ActiveFile::FilenameTest < ActiveSupport::TestCase +class ActiveVault::FilenameTest < ActiveSupport::TestCase test "sanitize" do "%$|:;/\t\r\n\\".each_char do |character| - filename = ActiveFile::Filename.new("foo#{character}bar.pdf") + filename = ActiveVault::Filename.new("foo#{character}bar.pdf") assert_equal 'foo-bar.pdf', filename.sanitized assert_equal 'foo-bar.pdf', filename.to_s end @@ -16,21 +16,21 @@ class ActiveFile::FilenameTest < ActiveSupport::TestCase "\xCF" => "�", "\x00" => "", }.each do |actual, expected| - assert_equal expected, ActiveFile::Filename.new(actual).sanitized + assert_equal expected, ActiveVault::Filename.new(actual).sanitized end end test "strips RTL override chars used to spoof unsafe executables as docs" do # Would be displayed in Windows as "evilexe.pdf" due to the right-to-left # (RTL) override char! - assert_equal 'evil-fdp.exe', ActiveFile::Filename.new("evil\u{202E}fdp.exe").sanitized + assert_equal 'evil-fdp.exe', ActiveVault::Filename.new("evil\u{202E}fdp.exe").sanitized end test "compare case-insensitively" do - assert_operator ActiveFile::Filename.new('foobar.pdf'), :==, ActiveFile::Filename.new('FooBar.PDF') + assert_operator ActiveVault::Filename.new('foobar.pdf'), :==, ActiveVault::Filename.new('FooBar.PDF') end test "compare sanitized" do - assert_operator ActiveFile::Filename.new('foo-bar.pdf'), :==, ActiveFile::Filename.new("foo\tbar.pdf") + assert_operator ActiveVault::Filename.new('foo-bar.pdf'), :==, ActiveVault::Filename.new("foo\tbar.pdf") end end diff --git a/test/site/disk_site_test.rb b/test/site/disk_site_test.rb index 63f12ad335..e9ebdcb0be 100644 --- a/test/site/disk_site_test.rb +++ b/test/site/disk_site_test.rb @@ -1,8 +1,8 @@ require "tmpdir" require "site/shared_site_tests" -class ActiveFile::Site::DiskSiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_file")) +class ActiveVault::Site::DiskSiteTest < ActiveSupport::TestCase + SITE = ActiveVault::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_vault")) - include ActiveFile::Site::SharedSiteTests + include ActiveVault::Site::SharedSiteTests end diff --git a/test/site/gcs_site_test.rb b/test/site/gcs_site_test.rb index c5f32a0595..56514ef136 100644 --- a/test/site/gcs_site_test.rb +++ b/test/site/gcs_site_test.rb @@ -1,10 +1,10 @@ require "site/shared_site_tests" if SITE_CONFIGURATIONS[:gcs] - class ActiveFile::Site::GCSSiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) + class ActiveVault::Site::GCSSiteTest < ActiveSupport::TestCase + SITE = ActiveVault::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) - include ActiveFile::Site::SharedSiteTests + include ActiveVault::Site::SharedSiteTests end else puts "Skipping GCS Site tests because no GCS configuration was supplied" diff --git a/test/site/s3_site_test.rb b/test/site/s3_site_test.rb index 7629b78ad5..6daeaac2ea 100644 --- a/test/site/s3_site_test.rb +++ b/test/site/s3_site_test.rb @@ -1,10 +1,10 @@ require "site/shared_site_tests" if SITE_CONFIGURATIONS[:s3] - class ActiveFile::Site::S3SiteTest < ActiveSupport::TestCase - SITE = ActiveFile::Site.configure(:S3, SITE_CONFIGURATIONS[:s3]) + class ActiveVault::Site::S3SiteTest < ActiveSupport::TestCase + SITE = ActiveVault::Site.configure(:S3, SITE_CONFIGURATIONS[:s3]) - include ActiveFile::Site::SharedSiteTests + include ActiveVault::Site::SharedSiteTests end else puts "Skipping S3 Site tests because no S3 configuration was supplied" diff --git a/test/site/shared_site_tests.rb b/test/site/shared_site_tests.rb index de1a54b874..56f1a13742 100644 --- a/test/site/shared_site_tests.rb +++ b/test/site/shared_site_tests.rb @@ -8,7 +8,7 @@ puts "Missing site configuration file in test/sites/configurations.yml" end -module ActiveFile::Site::SharedSiteTests +module ActiveVault::Site::SharedSiteTests extend ActiveSupport::Concern FIXTURE_KEY = SecureRandom.base58(24) diff --git a/test/test_helper.rb b/test/test_helper.rb index 9bb4a2fca1..96ef58b73f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,17 +4,17 @@ require "active_support/testing/autorun" require "byebug" -require "active_file" +require "active_vault" -require "active_file/site" -ActiveFile::Blob.site = ActiveFile::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_file")) +require "active_vault/site" +ActiveVault::Blob.site = ActiveVault::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_vault")) -require "active_file/verified_key_with_expiration" -ActiveFile::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") +require "active_vault/verified_key_with_expiration" +ActiveVault::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") class ActiveSupport::TestCase private def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") - ActiveFile::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type + ActiveVault::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type end end \ No newline at end of file diff --git a/test/verified_key_with_expiration_test.rb b/test/verified_key_with_expiration_test.rb index 8f145590d0..073bb047f6 100644 --- a/test/verified_key_with_expiration_test.rb +++ b/test/verified_key_with_expiration_test.rb @@ -1,19 +1,19 @@ require "test_helper" require "active_support/core_ext/securerandom" -class ActiveFile::VerifiedKeyWithExpirationTest < ActiveSupport::TestCase +class ActiveVault::VerifiedKeyWithExpirationTest < ActiveSupport::TestCase FIXTURE_KEY = SecureRandom.base58(24) test "without expiration" do - encoded_key = ActiveFile::VerifiedKeyWithExpiration.encode(FIXTURE_KEY) - assert_equal FIXTURE_KEY, ActiveFile::VerifiedKeyWithExpiration.decode(encoded_key) + encoded_key = ActiveVault::VerifiedKeyWithExpiration.encode(FIXTURE_KEY) + assert_equal FIXTURE_KEY, ActiveVault::VerifiedKeyWithExpiration.decode(encoded_key) end test "with expiration" do - encoded_key = ActiveFile::VerifiedKeyWithExpiration.encode(FIXTURE_KEY, expires_in: 1.minute) - assert_equal FIXTURE_KEY, ActiveFile::VerifiedKeyWithExpiration.decode(encoded_key) + encoded_key = ActiveVault::VerifiedKeyWithExpiration.encode(FIXTURE_KEY, expires_in: 1.minute) + assert_equal FIXTURE_KEY, ActiveVault::VerifiedKeyWithExpiration.decode(encoded_key) travel 2.minutes - assert_nil ActiveFile::VerifiedKeyWithExpiration.decode(encoded_key) + assert_nil ActiveVault::VerifiedKeyWithExpiration.decode(encoded_key) end end From f008fe3947e8f0ecd326efa18d14dd0363db93a1 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 13:09:01 +0200 Subject: [PATCH 072/289] Last name update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b4c7f2aaa..5160acda56 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Active File +# Active Vault ... @@ -44,4 +44,4 @@ end ## License -Active File is released under the [MIT License](https://opensource.org/licenses/MIT). +Active Vault is released under the [MIT License](https://opensource.org/licenses/MIT). From 3a92cbf6b358c983f3b1b9f628fb441e051a9984 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 15:07:22 +0200 Subject: [PATCH 073/289] Use active_vault as the table prefix At least pretend this can be used outside of Rails as well --- lib/active_vault/blob.rb | 2 +- lib/active_vault/migration.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_vault/blob.rb b/lib/active_vault/blob.rb index 4948d43ec7..d090d6dad2 100644 --- a/lib/active_vault/blob.rb +++ b/lib/active_vault/blob.rb @@ -3,7 +3,7 @@ # Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at class ActiveVault::Blob < ActiveRecord::Base - self.table_name = "rails_blobs" + self.table_name = "active_vault_blobs" has_secure_token :key store :metadata, coder: JSON diff --git a/lib/active_vault/migration.rb b/lib/active_vault/migration.rb index cc7a535f39..b3c66428ce 100644 --- a/lib/active_vault/migration.rb +++ b/lib/active_vault/migration.rb @@ -1,10 +1,10 @@ class ActiveVault::CreateBlobs < ActiveRecord::Migration[5.1] def change - create_table :rails_blobs do |t| t.string :key t.string :filename t.string :content_type t.text :metadata + create_table :active_vault_blobs do |t| t.integer :byte_size t.string :checksum t.time :created_at From 97aa328bb1e9d43bba1bcae2c8ddbaed397770c0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 15:18:42 +0200 Subject: [PATCH 074/289] Assign plain metadata for now --- lib/active_vault/blob.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/active_vault/blob.rb b/lib/active_vault/blob.rb index d090d6dad2..e2a6582e9f 100644 --- a/lib/active_vault/blob.rb +++ b/lib/active_vault/blob.rb @@ -15,6 +15,7 @@ def build_after_upload(io:, filename:, content_type: nil, metadata: nil) new.tap do |blob| blob.filename = filename blob.content_type = content_type + blob.metadata = metadata blob.upload io end From aaf841518866b34d769d9a951a389d1eef70d6e7 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 15:18:50 +0200 Subject: [PATCH 075/289] Add attachments --- README.md | 17 +------------- lib/active_vault/attachment.rb | 27 ++++++++++++++++++++++ lib/active_vault/attachments.rb | 30 +++++++++++++++++++++++++ lib/active_vault/migration.rb | 27 ++++++++++++++++------ lib/active_vault/railtie.rb | 8 +++++++ test/attachments_test.rb | 27 ++++++++++++++++++++++ test/database/create_users_migration.rb | 7 ++++++ test/database/setup.rb | 4 +++- test/test_helper.rb | 10 ++++++++- 9 files changed, 132 insertions(+), 25 deletions(-) create mode 100644 lib/active_vault/attachment.rb create mode 100644 lib/active_vault/attachments.rb create mode 100644 test/attachments_test.rb create mode 100644 test/database/create_users_migration.rb diff --git a/README.md b/README.md index 5160acda56..a72c79948f 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,11 @@ ```ruby class Person < ApplicationRecord - has_one :avatar -end - -class Avatar < ApplicationRecord - belongs_to :person - belongs_to :image, class_name: 'ActiveVault::Blob' - - has_file :image + has_file :avatar end avatar.image.url(expires_in: 5.minutes) - -class ActiveVault::DownloadsController < ActionController::Base - def show - head :ok, ActiveVault::Blob.locate(params[:id]).download_headers - end -end - - class AvatarsController < ApplicationController def create # @avatar = Avatar.create \ diff --git a/lib/active_vault/attachment.rb b/lib/active_vault/attachment.rb new file mode 100644 index 0000000000..eb108e9cbb --- /dev/null +++ b/lib/active_vault/attachment.rb @@ -0,0 +1,27 @@ +require "active_vault/blob" +require "global_id" +require "active_support/core_ext/module/delegation" + +# Schema: id, record_gid, blob_id, created_at +class ActiveVault::Attachment < ActiveRecord::Base + self.table_name = "active_vault_attachments" + + belongs_to :blob, class_name: "ActiveVault::Blob" + + delegate_missing_to :blob + + def record + @record ||= GlobalID::Locator.locate(record_gid) + end + + def record=(record) + @record = record + self.record_gid = record&.to_gid + end + + def purge + blob.purge + destroy + record.public_send "#{name}=", nil + end +end diff --git a/lib/active_vault/attachments.rb b/lib/active_vault/attachments.rb new file mode 100644 index 0000000000..c66c142650 --- /dev/null +++ b/lib/active_vault/attachments.rb @@ -0,0 +1,30 @@ +require "active_vault/attachment" +require "action_dispatch/http/upload" + +module ActiveVault::Attachments + def has_file(name) + define_method(name) do + (@active_vault_attachments ||= {})[name] ||= + ActiveVault::Attachment.find_by(record_gid: to_gid.to_s, name: name)&.tap { |a| a.record = self } + end + + define_method(:"#{name}=") do |attachable| + case attachable + when ActiveVault::Blob + blob = attachable + when ActionDispatch::Http::UploadedFile + blob = ActiveVault::Blob.create_after_upload! \ + io: attachable.open, + filename: attachable.original_filename, + content_type: attachable.content_type + when Hash + blob = ActiveVault::Blob.create_after_upload!(attachable) + when NilClass + blob = nil + end + + (@active_vault_attachments ||= {})[name] = blob ? + ActiveVault::Attachment.create!(record_gid: to_gid.to_s, name: name, blob: blob)&.tap { |a| a.record = self } : nil + end + end +end diff --git a/lib/active_vault/migration.rb b/lib/active_vault/migration.rb index b3c66428ce..985d26d1b9 100644 --- a/lib/active_vault/migration.rb +++ b/lib/active_vault/migration.rb @@ -1,15 +1,28 @@ -class ActiveVault::CreateBlobs < ActiveRecord::Migration[5.1] +class ActiveVault::CreateTables < ActiveRecord::Migration[5.1] def change - t.string :key - t.string :filename - t.string :content_type - t.text :metadata create_table :active_vault_blobs do |t| + t.string :key + t.string :filename + t.string :content_type + t.text :metadata t.integer :byte_size - t.string :checksum - t.time :created_at + t.string :checksum + t.time :created_at t.index [ :key ], unique: true end + + create_table :active_vault_attachments do |t| + t.string :name + t.string :record_gid + t.integer :blob_id + + t.time :created_at + + t.index :record_gid + t.index :blob_id + t.index [ :record_gid, :name ] + t.index [ :record_gid, :blob_id ], unique: true + end end end diff --git a/lib/active_vault/railtie.rb b/lib/active_vault/railtie.rb index c254f4c77c..f1c5740aa5 100644 --- a/lib/active_vault/railtie.rb +++ b/lib/active_vault/railtie.rb @@ -15,5 +15,13 @@ class Railtie < Rails::Railtie # :nodoc: end end end + + initializer "action_file.attachments" do + require "active_vault/attachments" + + ActiveSupport.on_load(:active_record) do + extend ActiveVault::Attachments + end + end end end diff --git a/test/attachments_test.rb b/test/attachments_test.rb new file mode 100644 index 0000000000..970804b68f --- /dev/null +++ b/test/attachments_test.rb @@ -0,0 +1,27 @@ +require "test_helper" +require "database/setup" +require "active_vault/blob" + +# ActiveRecord::Base.logger = Logger.new(STDOUT) + +class User < ActiveRecord::Base + has_file :avatar +end + +class ActiveVault::AttachmentsTest < ActiveSupport::TestCase + setup { @user = User.create!(name: "DHH") } + + test "create attachment from existing blob" do + @user.avatar = create_blob filename: "funky.jpg" + assert_equal "funky.jpg", @user.avatar.filename.to_s + end + + test "purge attached blob" do + @user.avatar = create_blob filename: "funky.jpg" + avatar_key = @user.avatar.key + + @user.avatar.purge + assert_nil @user.avatar + assert_not ActiveVault::Blob.site.exist?(avatar_key) + end +end diff --git a/test/database/create_users_migration.rb b/test/database/create_users_migration.rb new file mode 100644 index 0000000000..38dcdc129b --- /dev/null +++ b/test/database/create_users_migration.rb @@ -0,0 +1,7 @@ +class ActiveVault::CreateUsers < ActiveRecord::Migration[5.1] + def change + create_table :users do |t| + t.string :name + end + end +end diff --git a/test/database/setup.rb b/test/database/setup.rb index bc6e8b9ec1..7373d72237 100644 --- a/test/database/setup.rb +++ b/test/database/setup.rb @@ -1,4 +1,6 @@ require "active_vault/migration" +require_relative "create_users_migration" ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') -ActiveVault::CreateBlobs.migrate(:up) +ActiveVault::CreateTables.migrate(:up) +ActiveVault::CreateUsers.migrate(:up) diff --git a/test/test_helper.rb b/test/test_helper.rb index 96ef58b73f..29bd31e62f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -17,4 +17,12 @@ class ActiveSupport::TestCase def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") ActiveVault::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type end -end \ No newline at end of file +end + + +require "active_vault/attachments" +ActiveRecord::Base.send :extend, ActiveVault::Attachments + +require "global_id" +GlobalID.app = "ActiveVaultExampleApp" +ActiveRecord::Base.send :include, GlobalID::Identification From b7cc003aa0aada594cb18ab80c13c13c75bcd389 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 16:09:41 +0200 Subject: [PATCH 076/289] Attached one and many --- lib/active_vault/attached.rb | 34 ++++++++++++++++++++++ lib/active_vault/attached/macros.rb | 15 ++++++++++ lib/active_vault/attached/many.rb | 22 +++++++++++++++ lib/active_vault/attached/one.rb | 24 ++++++++++++++++ lib/active_vault/attachment.rb | 1 - lib/active_vault/attachments.rb | 30 -------------------- lib/active_vault/railtie.rb | 6 ++-- test/attachments_test.rb | 44 +++++++++++++++++++++++++---- test/test_helper.rb | 4 +-- 9 files changed, 139 insertions(+), 41 deletions(-) create mode 100644 lib/active_vault/attached.rb create mode 100644 lib/active_vault/attached/macros.rb create mode 100644 lib/active_vault/attached/many.rb create mode 100644 lib/active_vault/attached/one.rb delete mode 100644 lib/active_vault/attachments.rb diff --git a/lib/active_vault/attached.rb b/lib/active_vault/attached.rb new file mode 100644 index 0000000000..a968f3500d --- /dev/null +++ b/lib/active_vault/attached.rb @@ -0,0 +1,34 @@ +require "active_vault/blob" +require "active_vault/attachment" + +require "action_dispatch/http/upload" +require "active_support/core_ext/module/delegation" + +class ActiveVault::Attached + attr_reader :name, :record + + def initialize(name, record) + @name, @record = name, record + end + + private + def create_blob_from(attachable) + case attachable + when ActiveVault::Blob + attachable + when ActionDispatch::Http::UploadedFile + ActiveVault::Blob.create_after_upload! \ + io: attachable.open, + filename: attachable.original_filename, + content_type: attachable.content_type + when Hash + ActiveVault::Blob.create_after_upload!(attachable) + else + nil + end + end +end + +require "active_vault/attached/one" +require "active_vault/attached/many" +require "active_vault/attached/macros" diff --git a/lib/active_vault/attached/macros.rb b/lib/active_vault/attached/macros.rb new file mode 100644 index 0000000000..8cc84a95a9 --- /dev/null +++ b/lib/active_vault/attached/macros.rb @@ -0,0 +1,15 @@ +module ActiveVault::Attached::Macros + def has_one_attached(name) + define_method(name) do + instance_variable_get("@active_vault_attached_#{name}") || + instance_variable_set("@active_vault_attached_#{name}", ActiveVault::Attached::One.new(name, self)) + end + end + + def has_many_attached(name) + define_method(name) do + instance_variable_get("@active_vault_attached_#{name}") || + instance_variable_set("@active_vault_attached_#{name}", ActiveVault::Attached::Many.new(name, self)) + end + end +end diff --git a/lib/active_vault/attached/many.rb b/lib/active_vault/attached/many.rb new file mode 100644 index 0000000000..9f5f14ee85 --- /dev/null +++ b/lib/active_vault/attached/many.rb @@ -0,0 +1,22 @@ +class ActiveVault::Attached::Many < ActiveVault::Attached + delegate_missing_to :attachments + + def attachments + @attachments ||= ActiveVault::Attachment.where(record_gid: record.to_gid.to_s, name: name) + end + + def attach(*attachables) + @attachments = attachments + Array(attachables).collect do |attachable| + ActiveVault::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) + end + end + + def attached? + attachments.any? + end + + def purge + attachments.each(&:purge) + @attachments = nil + end +end diff --git a/lib/active_vault/attached/one.rb b/lib/active_vault/attached/one.rb new file mode 100644 index 0000000000..5566c1b971 --- /dev/null +++ b/lib/active_vault/attached/one.rb @@ -0,0 +1,24 @@ +class ActiveVault::Attached::One < ActiveVault::Attached + delegate_missing_to :attachment + + def attachment + @attachment ||= ActiveVault::Attachment.find_by(record_gid: record.to_gid.to_s, name: name) + end + + def attach(attachable) + if @attachment + # FIXME: Have options to declare dependent: :purge to clean up + end + + @attachment = ActiveVault::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) + end + + def attached? + attachment.present? + end + + def purge + attachment.purge + @attachment = nil + end +end diff --git a/lib/active_vault/attachment.rb b/lib/active_vault/attachment.rb index eb108e9cbb..1c96dabe31 100644 --- a/lib/active_vault/attachment.rb +++ b/lib/active_vault/attachment.rb @@ -22,6 +22,5 @@ def record=(record) def purge blob.purge destroy - record.public_send "#{name}=", nil end end diff --git a/lib/active_vault/attachments.rb b/lib/active_vault/attachments.rb deleted file mode 100644 index c66c142650..0000000000 --- a/lib/active_vault/attachments.rb +++ /dev/null @@ -1,30 +0,0 @@ -require "active_vault/attachment" -require "action_dispatch/http/upload" - -module ActiveVault::Attachments - def has_file(name) - define_method(name) do - (@active_vault_attachments ||= {})[name] ||= - ActiveVault::Attachment.find_by(record_gid: to_gid.to_s, name: name)&.tap { |a| a.record = self } - end - - define_method(:"#{name}=") do |attachable| - case attachable - when ActiveVault::Blob - blob = attachable - when ActionDispatch::Http::UploadedFile - blob = ActiveVault::Blob.create_after_upload! \ - io: attachable.open, - filename: attachable.original_filename, - content_type: attachable.content_type - when Hash - blob = ActiveVault::Blob.create_after_upload!(attachable) - when NilClass - blob = nil - end - - (@active_vault_attachments ||= {})[name] = blob ? - ActiveVault::Attachment.create!(record_gid: to_gid.to_s, name: name, blob: blob)&.tap { |a| a.record = self } : nil - end - end -end diff --git a/lib/active_vault/railtie.rb b/lib/active_vault/railtie.rb index f1c5740aa5..a0789f708f 100644 --- a/lib/active_vault/railtie.rb +++ b/lib/active_vault/railtie.rb @@ -16,11 +16,11 @@ class Railtie < Rails::Railtie # :nodoc: end end - initializer "action_file.attachments" do - require "active_vault/attachments" + initializer "action_file.attached" do + require "active_vault/attached" ActiveSupport.on_load(:active_record) do - extend ActiveVault::Attachments + extend ActiveVault::Attached::Macros end end end diff --git a/test/attachments_test.rb b/test/attachments_test.rb index 970804b68f..2e7e5d1a79 100644 --- a/test/attachments_test.rb +++ b/test/attachments_test.rb @@ -5,23 +5,57 @@ # ActiveRecord::Base.logger = Logger.new(STDOUT) class User < ActiveRecord::Base - has_file :avatar + has_one_attached :avatar + has_many_attached :highlights end class ActiveVault::AttachmentsTest < ActiveSupport::TestCase setup { @user = User.create!(name: "DHH") } - test "create attachment from existing blob" do - @user.avatar = create_blob filename: "funky.jpg" + teardown { ActiveVault::Blob.all.each(&:purge) } + + test "attach existing blob" do + @user.avatar.attach create_blob(filename: "funky.jpg") assert_equal "funky.jpg", @user.avatar.filename.to_s end + test "attach new blob" do + @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" + assert_equal "town.jpg", @user.avatar.filename.to_s + end + test "purge attached blob" do - @user.avatar = create_blob filename: "funky.jpg" + @user.avatar.attach create_blob(filename: "funky.jpg") avatar_key = @user.avatar.key @user.avatar.purge - assert_nil @user.avatar + assert_not @user.avatar.attached? assert_not ActiveVault::Blob.site.exist?(avatar_key) end + + test "attach existing blobs" do + @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") + + assert_equal "funky.jpg", @user.highlights.first.filename.to_s + assert_equal "wonky.jpg", @user.highlights.second.filename.to_s + end + + test "attach new blobs" do + @user.highlights.attach( + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) + + assert_equal "town.jpg", @user.highlights.first.filename.to_s + assert_equal "country.jpg", @user.highlights.second.filename.to_s + end + + test "purge attached blobs" do + @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") + highlight_keys = @user.highlights.collect(&:key) + + @user.highlights.purge + assert_not @user.highlights.attached? + assert_not ActiveVault::Blob.site.exist?(highlight_keys.first) + assert_not ActiveVault::Blob.site.exist?(highlight_keys.second) + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 29bd31e62f..b18613c365 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -20,8 +20,8 @@ def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text end -require "active_vault/attachments" -ActiveRecord::Base.send :extend, ActiveVault::Attachments +require "active_vault/attached" +ActiveRecord::Base.send :extend, ActiveVault::Attached::Macros require "global_id" GlobalID.app = "ActiveVaultExampleApp" From 3c9a28d6e4a56e4aae475dbf6051e4ee33150bba Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 16:10:03 +0200 Subject: [PATCH 077/289] Fix configuration names --- lib/active_vault/railtie.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/active_vault/railtie.rb b/lib/active_vault/railtie.rb index a0789f708f..1830780001 100644 --- a/lib/active_vault/railtie.rb +++ b/lib/active_vault/railtie.rb @@ -2,11 +2,11 @@ module ActiveVault class Railtie < Rails::Railtie # :nodoc: - config.action_file = ActiveSupport::OrderedOptions.new + config.active_vault = ActiveSupport::OrderedOptions.new config.eager_load_namespaces << ActiveVault - initializer "action_file.routes" do + initializer "active_vault.routes" do require "active_vault/disk_controller" config.after_initialize do |app| @@ -16,7 +16,7 @@ class Railtie < Rails::Railtie # :nodoc: end end - initializer "action_file.attached" do + initializer "active_vault.attached" do require "active_vault/attached" ActiveSupport.on_load(:active_record) do From c2dd4418f6c72358a54da48d7c30263180c69c71 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 16:28:45 +0200 Subject: [PATCH 078/289] Slim down examples --- README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a72c79948f..7fc1e43e07 100644 --- a/README.md +++ b/README.md @@ -5,24 +5,22 @@ ## Example ```ruby -class Person < ApplicationRecord - has_file :avatar +class User < ApplicationRecord + has_one_attached :avatar end -avatar.image.url(expires_in: 5.minutes) +user.avatar.attach io: File.open("~/face.jpg"), filename: "avatar.jpg", content_type: "image/jpg" +user.avatar.exist? # => true + +user.avatar.purge +user.avatar.exist? # => false + +user.image.url(expires_in: 5.minutes) # => /rails/blobs/ class AvatarsController < ApplicationController - def create - # @avatar = Avatar.create \ - # image: ActiveVault::Blob.save!(file_name: params.require(:name), content_type: request.content_type, data: request.body) - @avatar = Avatar.create! image: Avatar.image.extract_from(request) - end -end - - -class ProfilesController < ApplicationController def update - @person.update! avatar: @person.avatar.update!(image: ) + Current.user.avatar.attach(params.require(:avatar)) + redirect_to Current.user end end ``` From 5276323d40e816ba4ebb3bb9b30d7d384773b7fe Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 16:47:39 +0200 Subject: [PATCH 079/289] Ensure the array is flat --- lib/active_vault/attached/many.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_vault/attached/many.rb b/lib/active_vault/attached/many.rb index 9f5f14ee85..49c6aae575 100644 --- a/lib/active_vault/attached/many.rb +++ b/lib/active_vault/attached/many.rb @@ -6,7 +6,7 @@ def attachments end def attach(*attachables) - @attachments = attachments + Array(attachables).collect do |attachable| + @attachments = attachments + Array(attachables).flatten.collect do |attachable| ActiveVault::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) end end From 04dad4ee83e4594a9fac1112adcfc8a8f0284cad Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Wed, 5 Jul 2017 11:23:56 -0400 Subject: [PATCH 080/289] Fix test --- test/site/gcs_site_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/site/gcs_site_test.rb b/test/site/gcs_site_test.rb index 3185c43f3c..b3712a2116 100644 --- a/test/site/gcs_site_test.rb +++ b/test/site/gcs_site_test.rb @@ -8,7 +8,7 @@ class ActiveVault::Site::GCSSiteTest < ActiveSupport::TestCase test "signed URL generation" do travel_to Time.now do - url = SITE.bucket.signed_url(path: FIXTURE_KEY, expires: 120) + + url = SITE.bucket.signed_url(FIXTURE_KEY, expires: 120) + "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" assert_equal url, @site.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") From ac796b8d9295fa4a24b384f8ec0410c21276c493 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Wed, 5 Jul 2017 11:24:32 -0400 Subject: [PATCH 081/289] Require the Active Support core extension used --- lib/active_vault/site/gcs_site.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/active_vault/site/gcs_site.rb b/lib/active_vault/site/gcs_site.rb index 49cbf5d9cb..8f51d486ec 100644 --- a/lib/active_vault/site/gcs_site.rb +++ b/lib/active_vault/site/gcs_site.rb @@ -1,4 +1,5 @@ require "google/cloud/storage" +require "active_support/core_ext/object/to_query" class ActiveVault::Site::GCSSite < ActiveVault::Site attr_reader :client, :bucket From 7d3955e6f7c9a38a226dff81bb9b436bae91590d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 18:29:43 +0200 Subject: [PATCH 082/289] Avoid duplicate attachments --- lib/active_vault/attached/many.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_vault/attached/many.rb b/lib/active_vault/attached/many.rb index 49c6aae575..83fab12385 100644 --- a/lib/active_vault/attached/many.rb +++ b/lib/active_vault/attached/many.rb @@ -6,7 +6,7 @@ def attachments end def attach(*attachables) - @attachments = attachments + Array(attachables).flatten.collect do |attachable| + @attachments = attachments | Array(attachables).flatten.collect do |attachable| ActiveVault::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) end end From eefbdc2b9eb19d48b22475d11bbb87988c54475e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 18:30:00 +0200 Subject: [PATCH 083/289] Only purge if attached --- lib/active_vault/attached/many.rb | 13 +++++++++++-- lib/active_vault/attached/one.rb | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/active_vault/attached/many.rb b/lib/active_vault/attached/many.rb index 83fab12385..6f79a1c555 100644 --- a/lib/active_vault/attached/many.rb +++ b/lib/active_vault/attached/many.rb @@ -16,7 +16,16 @@ def attached? end def purge - attachments.each(&:purge) - @attachments = nil + if attached? + attachments.each(&:purge) + @attachments = nil + end + end + + def purge_later + if attached? + attachments.each(&:purge_later) + @attachments = nil + end end end diff --git a/lib/active_vault/attached/one.rb b/lib/active_vault/attached/one.rb index 5566c1b971..9bf83254c0 100644 --- a/lib/active_vault/attached/one.rb +++ b/lib/active_vault/attached/one.rb @@ -18,7 +18,16 @@ def attached? end def purge - attachment.purge - @attachment = nil + if attached? + attachment.purge + @attachment = nil + end + end + + def purge_later + if attached? + attachment.purge_later + @attachment = nil + end end end From 5492be52103d2237a8782a86f0f6f89d2fb5a06e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 18:30:15 +0200 Subject: [PATCH 084/289] Bit further on the README --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fc1e43e07..4a554ac18f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ # Active Vault -... +Active Vault makes it simple to upload and reference files in cloud sites, like Amazon S3 or Google Cloud Storage, +and attach those files to Active Records. It also provides a disk site for testing or local deployments, but the +focus is on cloud storage. ## Example +One attachment: + ```ruby class User < ApplicationRecord has_one_attached :avatar @@ -25,6 +29,38 @@ class AvatarsController < ApplicationController end ``` +Many attachments: + +```ruby +class Message < ApplicationRecord + has_many_attached :images +end + +<%= form_with model: @message do |form| %> + <%= form.text_field :title, placeholder: "Title" %>
+ <%= form.text_area :content %>

+ + <%= form.file_field :images, multiple: true %>
+ <%= form.submit %> +<% end %> + +class MessagesController < ApplicationController + def create + message = Message.create! params.require(:message).permit(:title, :content) + message.images.attach(params[:message][:images]) + redirect_to message + end +end +``` + +## Configuration + +Add `require "active_vault"` to config/application.rb and create a `config/initializers/active_vault_sites.rb` with the following: + +```ruby + +``` + ## License Active Vault is released under the [MIT License](https://opensource.org/licenses/MIT). From c2fa570e2ec853c2e29325cbc4e90d03f7095f22 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 18:30:29 +0200 Subject: [PATCH 085/289] Moving this to the macro definition --- lib/active_vault/attached/one.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/active_vault/attached/one.rb b/lib/active_vault/attached/one.rb index 9bf83254c0..01a5d0d6f0 100644 --- a/lib/active_vault/attached/one.rb +++ b/lib/active_vault/attached/one.rb @@ -6,10 +6,6 @@ def attachment end def attach(attachable) - if @attachment - # FIXME: Have options to declare dependent: :purge to clean up - end - @attachment = ActiveVault::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) end From e3ade5fd2dc76235ecc98999f44217c470eb72e1 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 18:31:49 +0200 Subject: [PATCH 086/289] Default to purging later when the owning record is destroyed --- lib/active_vault/attached/macros.rb | 12 ++++++++-- lib/active_vault/attachment.rb | 4 ++++ lib/active_vault/blob.rb | 1 + lib/active_vault/purge_job.rb | 11 ++++++---- test/attachments_test.rb | 34 +++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/lib/active_vault/attached/macros.rb b/lib/active_vault/attached/macros.rb index 8cc84a95a9..1b95c14c9c 100644 --- a/lib/active_vault/attached/macros.rb +++ b/lib/active_vault/attached/macros.rb @@ -1,15 +1,23 @@ module ActiveVault::Attached::Macros - def has_one_attached(name) + def has_one_attached(name, dependent: :purge_later) define_method(name) do instance_variable_get("@active_vault_attached_#{name}") || instance_variable_set("@active_vault_attached_#{name}", ActiveVault::Attached::One.new(name, self)) end + + if dependent == :purge_later + before_destroy { public_send(name).purge_later } + end end - def has_many_attached(name) + def has_many_attached(name, dependent: :purge_later) define_method(name) do instance_variable_get("@active_vault_attached_#{name}") || instance_variable_set("@active_vault_attached_#{name}", ActiveVault::Attached::Many.new(name, self)) end + + if dependent == :purge_later + before_destroy { public_send(name).purge_later } + end end end diff --git a/lib/active_vault/attachment.rb b/lib/active_vault/attachment.rb index 1c96dabe31..549a734d68 100644 --- a/lib/active_vault/attachment.rb +++ b/lib/active_vault/attachment.rb @@ -23,4 +23,8 @@ def purge blob.purge destroy end + + def purge_later + ActiveVault::PurgeJob.perform_later(self) + end end diff --git a/lib/active_vault/blob.rb b/lib/active_vault/blob.rb index e2a6582e9f..a232ca5c1a 100644 --- a/lib/active_vault/blob.rb +++ b/lib/active_vault/blob.rb @@ -1,5 +1,6 @@ require "active_vault/site" require "active_vault/filename" +require "active_vault/purge_job" # Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at class ActiveVault::Blob < ActiveRecord::Base diff --git a/lib/active_vault/purge_job.rb b/lib/active_vault/purge_job.rb index d7634af2bb..b68eb370bb 100644 --- a/lib/active_vault/purge_job.rb +++ b/lib/active_vault/purge_job.rb @@ -1,7 +1,10 @@ -class ActiveVault::PurgeJob < ActiveJob::Base - retry_on ActiveVault::StorageException +require "active_job" - def perform(blob) - blob.purge +class ActiveVault::PurgeJob < ActiveJob::Base + # FIXME: Limit this to a custom ActiveVault error + retry_on StandardError + + def perform(attachment_or_blob) + attachment_or_blob.purge end end diff --git a/test/attachments_test.rb b/test/attachments_test.rb index 2e7e5d1a79..1b784b50c1 100644 --- a/test/attachments_test.rb +++ b/test/attachments_test.rb @@ -2,6 +2,10 @@ require "database/setup" require "active_vault/blob" +require "active_job" +ActiveJob::Base.queue_adapter = :test +ActiveJob::Base.logger = nil + # ActiveRecord::Base.logger = Logger.new(STDOUT) class User < ActiveRecord::Base @@ -10,6 +14,8 @@ class User < ActiveRecord::Base end class ActiveVault::AttachmentsTest < ActiveSupport::TestCase + include ActiveJob::TestHelper + setup { @user = User.create!(name: "DHH") } teardown { ActiveVault::Blob.all.each(&:purge) } @@ -33,6 +39,19 @@ class ActiveVault::AttachmentsTest < ActiveSupport::TestCase assert_not ActiveVault::Blob.site.exist?(avatar_key) end + test "purge attached blob later when the record is destroyed" do + @user.avatar.attach create_blob(filename: "funky.jpg") + avatar_key = @user.avatar.key + + perform_enqueued_jobs do + @user.destroy + + assert_nil ActiveVault::Blob.find_by(key: avatar_key) + assert_not ActiveVault::Blob.site.exist?(avatar_key) + end + end + + test "attach existing blobs" do @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") @@ -58,4 +77,19 @@ class ActiveVault::AttachmentsTest < ActiveSupport::TestCase assert_not ActiveVault::Blob.site.exist?(highlight_keys.first) assert_not ActiveVault::Blob.site.exist?(highlight_keys.second) end + + test "purge attached blobs later when the record is destroyed" do + @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") + highlight_keys = @user.highlights.collect(&:key) + + perform_enqueued_jobs do + @user.destroy + + assert_nil ActiveVault::Blob.find_by(key: highlight_keys.first) + assert_not ActiveVault::Blob.site.exist?(highlight_keys.first) + + assert_nil ActiveVault::Blob.find_by(key: highlight_keys.second) + assert_not ActiveVault::Blob.site.exist?(highlight_keys.second) + end + end end From 54886cb7b0754fb4c09febf1b70dd6eae48995cf Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 18:44:58 +0200 Subject: [PATCH 087/289] Record outstanding todos --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 4a554ac18f..bb6e9c1e34 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,17 @@ Add `require "active_vault"` to config/application.rb and create a `config/initi ``` +## Todos + +- Strip Download of its resposibilities and delete class +- Proper logging +- MirrorSite +- Read metadata via Marcel? +- Copy over migration to app via rake task +- Add Migrator to copy/move between sites +- Explore direct uploads to cloud +- Extract VerifiedKeyWithExpiration into Rails as a feature of MessageVerifier + ## License Active Vault is released under the [MIT License](https://opensource.org/licenses/MIT). From abda6d784eb0940b352cd28c28a3f3e87757a489 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 18:57:45 +0200 Subject: [PATCH 088/289] Basic MirrorSite Still need to convert it to threading --- README.md | 2 +- lib/active_vault/site/mirror_site.rb | 9 ++++++--- test/site/mirror_site_test.rb | 30 ++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 test/site/mirror_site_test.rb diff --git a/README.md b/README.md index bb6e9c1e34..97101c6099 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Add `require "active_vault"` to config/application.rb and create a `config/initi - Strip Download of its resposibilities and delete class - Proper logging -- MirrorSite +- Convert MirrorSite to use threading - Read metadata via Marcel? - Copy over migration to app via rake task - Add Migrator to copy/move between sites diff --git a/lib/active_vault/site/mirror_site.rb b/lib/active_vault/site/mirror_site.rb index 67d79a2607..62b2f20586 100644 --- a/lib/active_vault/site/mirror_site.rb +++ b/lib/active_vault/site/mirror_site.rb @@ -6,7 +6,10 @@ def initialize(sites:) end def upload(key, io) - perform_across_sites :upload, key, io + sites.collect do |site| + site.upload key, io + io.rewind + end end def download(key) @@ -35,10 +38,10 @@ def primary_site sites.first end - def perform_across_sites(method, **args) + def perform_across_sites(method, *args) # FIXME: Convert to be threaded sites.collect do |site| - site.send method, **args + site.public_send method, *args end end end diff --git a/test/site/mirror_site_test.rb b/test/site/mirror_site_test.rb new file mode 100644 index 0000000000..326edb2f9c --- /dev/null +++ b/test/site/mirror_site_test.rb @@ -0,0 +1,30 @@ +require "tmpdir" +require "site/shared_site_tests" + +class ActiveVault::Site::MirrorSiteTest < ActiveSupport::TestCase + PRIMARY_DISK_SITE = ActiveVault::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_vault")) + SECONDARY_DISK_SITE = ActiveVault::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_vault_mirror")) + + SITE = ActiveVault::Site.configure :Mirror, sites: [ PRIMARY_DISK_SITE, SECONDARY_DISK_SITE ] + + include ActiveVault::Site::SharedSiteTests + + test "uploading was done to all sites" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + io = StringIO.new(data) + @site.upload(key, io) + + assert_equal data, PRIMARY_DISK_SITE.download(key) + assert_equal data, SECONDARY_DISK_SITE.download(key) + ensure + @site.delete key + end + end + + test "existing in all sites" do + assert PRIMARY_DISK_SITE.exist?(FIXTURE_KEY) + assert SECONDARY_DISK_SITE.exist?(FIXTURE_KEY) + end +end From 5869045f2e71f0abdf3add19629d23a46b9fff0d Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Wed, 5 Jul 2017 18:31:19 -0400 Subject: [PATCH 089/289] ActiveVault::Site::MirrorSite#url --- lib/active_vault/site/mirror_site.rb | 8 ++++++-- test/site/mirror_site_test.rb | 9 ++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/active_vault/site/mirror_site.rb b/lib/active_vault/site/mirror_site.rb index 62b2f20586..8a2fa52fcb 100644 --- a/lib/active_vault/site/mirror_site.rb +++ b/lib/active_vault/site/mirror_site.rb @@ -9,7 +9,7 @@ def upload(key, io) sites.collect do |site| site.upload key, io io.rewind - end + end end def download(key) @@ -25,6 +25,10 @@ def exist?(key) end + def url(key, **options) + primary_site.url(key, **options) + end + def byte_size(key) primary_site.byte_size(key) end @@ -42,6 +46,6 @@ def perform_across_sites(method, *args) # FIXME: Convert to be threaded sites.collect do |site| site.public_send method, *args - end + end end end diff --git a/test/site/mirror_site_test.rb b/test/site/mirror_site_test.rb index 326edb2f9c..bdb0b4c357 100644 --- a/test/site/mirror_site_test.rb +++ b/test/site/mirror_site_test.rb @@ -9,7 +9,7 @@ class ActiveVault::Site::MirrorSiteTest < ActiveSupport::TestCase include ActiveVault::Site::SharedSiteTests - test "uploading was done to all sites" do + test "uploading to all sites" do begin key = SecureRandom.base58(24) data = "Something else entirely!" @@ -27,4 +27,11 @@ class ActiveVault::Site::MirrorSiteTest < ActiveSupport::TestCase assert PRIMARY_DISK_SITE.exist?(FIXTURE_KEY) assert SECONDARY_DISK_SITE.exist?(FIXTURE_KEY) end + + test "URL generation for primary site" do + travel_to Time.now do + assert_equal PRIMARY_DISK_SITE.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "test.txt"), + SITE.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "test.txt") + end + end end From c624df326a4ef36919a5195a3c5509fab97dcba3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 11:33:29 +0200 Subject: [PATCH 090/289] ActiveVault -> ActiveStorage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yaroslav agreed to hand over the gem name ❤️ --- Gemfile.lock | 4 +-- README.md | 4 +-- activevault.gemspec => activestorage.gemspec | 4 +-- lib/{active_vault.rb => active_storage.rb} | 4 +-- .../attached.rb | 18 ++++++------- lib/active_storage/attached/macros.rb | 23 ++++++++++++++++ .../attached/many.rb | 6 ++--- .../attached/one.rb | 6 ++--- .../attachment.rb | 10 +++---- lib/{active_vault => active_storage}/blob.rb | 14 +++++----- .../config/sites.yml | 6 ++--- .../disk_controller.rb | 10 +++---- .../download.rb | 2 +- .../filename.rb | 2 +- .../migration.rb | 6 ++--- .../purge_job.rb | 4 +-- lib/active_storage/railtie.rb | 27 +++++++++++++++++++ lib/{active_vault => active_storage}/site.rb | 6 ++--- .../site/disk_site.rb | 4 +-- .../site/gcs_site.rb | 2 +- .../site/mirror_site.rb | 2 +- .../site/s3_site.rb | 2 +- .../verified_key_with_expiration.rb | 4 +-- lib/active_vault/attached/macros.rb | 23 ---------------- lib/active_vault/railtie.rb | 27 ------------------- test/attachments_test.rb | 24 ++++++++--------- test/blob_test.rb | 6 ++--- test/database/create_users_migration.rb | 2 +- test/database/setup.rb | 6 ++--- test/disk_controller_test.rb | 14 +++++----- test/filename_test.rb | 12 ++++----- test/site/disk_site_test.rb | 6 ++--- test/site/gcs_site_test.rb | 6 ++--- test/site/mirror_site_test.rb | 19 +++++-------- test/site/s3_site_test.rb | 6 ++--- test/site/shared_site_tests.rb | 2 +- test/test_helper.rb | 18 ++++++------- test/verified_key_with_expiration_test.rb | 12 ++++----- 38 files changed, 173 insertions(+), 180 deletions(-) rename activevault.gemspec => activestorage.gemspec (86%) rename lib/{active_vault.rb => active_storage.rb} (57%) rename lib/{active_vault => active_storage}/attached.rb (59%) create mode 100644 lib/active_storage/attached/macros.rb rename lib/{active_vault => active_storage}/attached/many.rb (61%) rename lib/{active_vault => active_storage}/attached/one.rb (53%) rename lib/{active_vault => active_storage}/attachment.rb (62%) rename lib/{active_vault => active_storage}/blob.rb (81%) rename lib/{active_vault => active_storage}/config/sites.yml (78%) rename lib/{active_vault => active_storage}/disk_controller.rb (64%) rename lib/{active_vault => active_storage}/download.rb (98%) rename lib/{active_vault => active_storage}/filename.rb (94%) rename lib/{active_vault => active_storage}/migration.rb (75%) rename lib/{active_vault => active_storage}/purge_job.rb (54%) create mode 100644 lib/active_storage/railtie.rb rename lib/{active_vault => active_storage}/site.rb (80%) rename lib/{active_vault => active_storage}/site/disk_site.rb (89%) rename lib/{active_vault => active_storage}/site/gcs_site.rb (94%) rename lib/{active_vault => active_storage}/site/mirror_site.rb (93%) rename lib/{active_vault => active_storage}/site/s3_site.rb (96%) rename lib/{active_vault => active_storage}/verified_key_with_expiration.rb (84%) delete mode 100644 lib/active_vault/attached/macros.rb delete mode 100644 lib/active_vault/railtie.rb diff --git a/Gemfile.lock b/Gemfile.lock index e20ba22218..afef3518aa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - activevault (0.1) + activestorage (0.1) actionpack (>= 5.1) activejob (>= 5.1) activerecord (>= 5.1) @@ -223,7 +223,7 @@ PLATFORMS ruby DEPENDENCIES - activevault! + activestorage! aws-sdk bundler (~> 1.15) byebug diff --git a/README.md b/README.md index 97101c6099..8803cd9f3b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ user.avatar.exist? # => true user.avatar.purge user.avatar.exist? # => false -user.image.url(expires_in: 5.minutes) # => /rails/blobs/ +user.avatar.url(expires_in: 5.minutes) # => /rails/blobs/ class AvatarsController < ApplicationController def update @@ -55,7 +55,7 @@ end ## Configuration -Add `require "active_vault"` to config/application.rb and create a `config/initializers/active_vault_sites.rb` with the following: +Add `require "active_storage"` to config/application.rb and create a `config/initializers/active_storage_sites.rb` with the following: ```ruby diff --git a/activevault.gemspec b/activestorage.gemspec similarity index 86% rename from activevault.gemspec rename to activestorage.gemspec index 7144563d18..4670bd1502 100644 --- a/activevault.gemspec +++ b/activestorage.gemspec @@ -1,10 +1,10 @@ Gem::Specification.new do |s| - s.name = "activevault" + s.name = "activestorage" s.version = "0.1" s.authors = "David Heinemeier Hansson" s.email = "david@basecamp.com" s.summary = "Store files in Rails applications" - s.homepage = "https://github.com/rails/activevault" + s.homepage = "https://github.com/rails/activestorage" s.license = "MIT" s.required_ruby_version = ">= 1.9.3" diff --git a/lib/active_vault.rb b/lib/active_storage.rb similarity index 57% rename from lib/active_vault.rb rename to lib/active_storage.rb index f47b09b4cd..e87eb8a506 100644 --- a/lib/active_vault.rb +++ b/lib/active_storage.rb @@ -1,7 +1,7 @@ require "active_record" -require "active_vault/railtie" if defined?(Rails) +require "active_storage/railtie" if defined?(Rails) -module ActiveVault +module ActiveStorage extend ActiveSupport::Autoload autoload :Blob diff --git a/lib/active_vault/attached.rb b/lib/active_storage/attached.rb similarity index 59% rename from lib/active_vault/attached.rb rename to lib/active_storage/attached.rb index a968f3500d..7475c38999 100644 --- a/lib/active_vault/attached.rb +++ b/lib/active_storage/attached.rb @@ -1,10 +1,10 @@ -require "active_vault/blob" -require "active_vault/attachment" +require "active_storage/blob" +require "active_storage/attachment" require "action_dispatch/http/upload" require "active_support/core_ext/module/delegation" -class ActiveVault::Attached +class ActiveStorage::Attached attr_reader :name, :record def initialize(name, record) @@ -14,21 +14,21 @@ def initialize(name, record) private def create_blob_from(attachable) case attachable - when ActiveVault::Blob + when ActiveStorage::Blob attachable when ActionDispatch::Http::UploadedFile - ActiveVault::Blob.create_after_upload! \ + ActiveStorage::Blob.create_after_upload! \ io: attachable.open, filename: attachable.original_filename, content_type: attachable.content_type when Hash - ActiveVault::Blob.create_after_upload!(attachable) + ActiveStorage::Blob.create_after_upload!(attachable) else nil end end end -require "active_vault/attached/one" -require "active_vault/attached/many" -require "active_vault/attached/macros" +require "active_storage/attached/one" +require "active_storage/attached/many" +require "active_storage/attached/macros" diff --git a/lib/active_storage/attached/macros.rb b/lib/active_storage/attached/macros.rb new file mode 100644 index 0000000000..96493d1215 --- /dev/null +++ b/lib/active_storage/attached/macros.rb @@ -0,0 +1,23 @@ +module ActiveStorage::Attached::Macros + def has_one_attached(name, dependent: :purge_later) + define_method(name) do + instance_variable_get("@active_storage_attached_#{name}") || + instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::One.new(name, self)) + end + + if dependent == :purge_later + before_destroy { public_send(name).purge_later } + end + end + + def has_many_attached(name, dependent: :purge_later) + define_method(name) do + instance_variable_get("@active_storage_attached_#{name}") || + instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::Many.new(name, self)) + end + + if dependent == :purge_later + before_destroy { public_send(name).purge_later } + end + end +end diff --git a/lib/active_vault/attached/many.rb b/lib/active_storage/attached/many.rb similarity index 61% rename from lib/active_vault/attached/many.rb rename to lib/active_storage/attached/many.rb index 6f79a1c555..f1535dfbc6 100644 --- a/lib/active_vault/attached/many.rb +++ b/lib/active_storage/attached/many.rb @@ -1,13 +1,13 @@ -class ActiveVault::Attached::Many < ActiveVault::Attached +class ActiveStorage::Attached::Many < ActiveStorage::Attached delegate_missing_to :attachments def attachments - @attachments ||= ActiveVault::Attachment.where(record_gid: record.to_gid.to_s, name: name) + @attachments ||= ActiveStorage::Attachment.where(record_gid: record.to_gid.to_s, name: name) end def attach(*attachables) @attachments = attachments | Array(attachables).flatten.collect do |attachable| - ActiveVault::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) + ActiveStorage::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) end end diff --git a/lib/active_vault/attached/one.rb b/lib/active_storage/attached/one.rb similarity index 53% rename from lib/active_vault/attached/one.rb rename to lib/active_storage/attached/one.rb index 01a5d0d6f0..d08d265992 100644 --- a/lib/active_vault/attached/one.rb +++ b/lib/active_storage/attached/one.rb @@ -1,12 +1,12 @@ -class ActiveVault::Attached::One < ActiveVault::Attached +class ActiveStorage::Attached::One < ActiveStorage::Attached delegate_missing_to :attachment def attachment - @attachment ||= ActiveVault::Attachment.find_by(record_gid: record.to_gid.to_s, name: name) + @attachment ||= ActiveStorage::Attachment.find_by(record_gid: record.to_gid.to_s, name: name) end def attach(attachable) - @attachment = ActiveVault::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) + @attachment = ActiveStorage::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) end def attached? diff --git a/lib/active_vault/attachment.rb b/lib/active_storage/attachment.rb similarity index 62% rename from lib/active_vault/attachment.rb rename to lib/active_storage/attachment.rb index 549a734d68..20c619aa5a 100644 --- a/lib/active_vault/attachment.rb +++ b/lib/active_storage/attachment.rb @@ -1,12 +1,12 @@ -require "active_vault/blob" +require "active_storage/blob" require "global_id" require "active_support/core_ext/module/delegation" # Schema: id, record_gid, blob_id, created_at -class ActiveVault::Attachment < ActiveRecord::Base - self.table_name = "active_vault_attachments" +class ActiveStorage::Attachment < ActiveRecord::Base + self.table_name = "active_storage_attachments" - belongs_to :blob, class_name: "ActiveVault::Blob" + belongs_to :blob, class_name: "ActiveStorage::Blob" delegate_missing_to :blob @@ -25,6 +25,6 @@ def purge end def purge_later - ActiveVault::PurgeJob.perform_later(self) + ActiveStorage::PurgeJob.perform_later(self) end end diff --git a/lib/active_vault/blob.rb b/lib/active_storage/blob.rb similarity index 81% rename from lib/active_vault/blob.rb rename to lib/active_storage/blob.rb index a232ca5c1a..edf57b5c78 100644 --- a/lib/active_vault/blob.rb +++ b/lib/active_storage/blob.rb @@ -1,10 +1,10 @@ -require "active_vault/site" -require "active_vault/filename" -require "active_vault/purge_job" +require "active_storage/site" +require "active_storage/filename" +require "active_storage/purge_job" # Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at -class ActiveVault::Blob < ActiveRecord::Base - self.table_name = "active_vault_blobs" +class ActiveStorage::Blob < ActiveRecord::Base + self.table_name = "active_storage_blobs" has_secure_token :key store :metadata, coder: JSON @@ -33,7 +33,7 @@ def key end def filename - ActiveVault::Filename.new(self[:filename]) + ActiveStorage::Filename.new(self[:filename]) end def url(expires_in: 5.minutes, disposition: :inline) @@ -63,6 +63,6 @@ def purge end def purge_later - ActiveVault::PurgeJob.perform_later(self) + ActiveStorage::PurgeJob.perform_later(self) end end diff --git a/lib/active_vault/config/sites.yml b/lib/active_storage/config/sites.yml similarity index 78% rename from lib/active_vault/config/sites.yml rename to lib/active_storage/config/sites.yml index 334e779b28..43bc77fbf9 100644 --- a/lib/active_vault/config/sites.yml +++ b/lib/active_storage/config/sites.yml @@ -1,13 +1,13 @@ # Configuration should be something like this: # # config/environments/development.rb -# config.active_vault.site = :local +# config.active_storage.site = :local # # config/environments/production.rb -# config.active_vault.site = :amazon +# config.active_storage.site = :amazon local: site: Disk - root: <%%= File.join(Dir.tmpdir, "active_vault") %> + root: <%%= File.join(Dir.tmpdir, "active_storage") %> amazon: site: S3 diff --git a/lib/active_vault/disk_controller.rb b/lib/active_storage/disk_controller.rb similarity index 64% rename from lib/active_vault/disk_controller.rb rename to lib/active_storage/disk_controller.rb index 623569f0f6..3eba86c213 100644 --- a/lib/active_vault/disk_controller.rb +++ b/lib/active_storage/disk_controller.rb @@ -1,13 +1,13 @@ require "action_controller" -require "active_vault/blob" -require "active_vault/verified_key_with_expiration" +require "active_storage/blob" +require "active_storage/verified_key_with_expiration" require "active_support/core_ext/object/inclusion" -class ActiveVault::DiskController < ActionController::Base +class ActiveStorage::DiskController < ActionController::Base def show if key = decode_verified_key - blob = ActiveVault::Blob.find_by!(key: key) + blob = ActiveStorage::Blob.find_by!(key: key) if stale?(etag: blob.checksum) send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param @@ -19,7 +19,7 @@ def show private def decode_verified_key - ActiveVault::VerifiedKeyWithExpiration.decode(params[:encoded_key]) + ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key]) end def disposition_param diff --git a/lib/active_vault/download.rb b/lib/active_storage/download.rb similarity index 98% rename from lib/active_vault/download.rb rename to lib/active_storage/download.rb index 6e74056062..4d656942d8 100644 --- a/lib/active_vault/download.rb +++ b/lib/active_storage/download.rb @@ -1,4 +1,4 @@ -class ActiveVault::Download +class ActiveStorage::Download # Sending .ai files as application/postscript to Safari opens them in a blank, grey screen. # Downloading .ai as application/postscript files in Safari appends .ps to the extension. # Sending HTML, SVG, XML and SWF files as binary closes XSS vulnerabilities. diff --git a/lib/active_vault/filename.rb b/lib/active_storage/filename.rb similarity index 94% rename from lib/active_vault/filename.rb rename to lib/active_storage/filename.rb index 647d037b1f..71614b5113 100644 --- a/lib/active_vault/filename.rb +++ b/lib/active_storage/filename.rb @@ -1,4 +1,4 @@ -class ActiveVault::Filename +class ActiveStorage::Filename include Comparable def initialize(filename) diff --git a/lib/active_vault/migration.rb b/lib/active_storage/migration.rb similarity index 75% rename from lib/active_vault/migration.rb rename to lib/active_storage/migration.rb index 985d26d1b9..c0400abe3b 100644 --- a/lib/active_vault/migration.rb +++ b/lib/active_storage/migration.rb @@ -1,6 +1,6 @@ -class ActiveVault::CreateTables < ActiveRecord::Migration[5.1] +class ActiveStorage::CreateTables < ActiveRecord::Migration[5.1] def change - create_table :active_vault_blobs do |t| + create_table :active_storage_blobs do |t| t.string :key t.string :filename t.string :content_type @@ -12,7 +12,7 @@ def change t.index [ :key ], unique: true end - create_table :active_vault_attachments do |t| + create_table :active_storage_attachments do |t| t.string :name t.string :record_gid t.integer :blob_id diff --git a/lib/active_vault/purge_job.rb b/lib/active_storage/purge_job.rb similarity index 54% rename from lib/active_vault/purge_job.rb rename to lib/active_storage/purge_job.rb index b68eb370bb..b59d3687f8 100644 --- a/lib/active_vault/purge_job.rb +++ b/lib/active_storage/purge_job.rb @@ -1,7 +1,7 @@ require "active_job" -class ActiveVault::PurgeJob < ActiveJob::Base - # FIXME: Limit this to a custom ActiveVault error +class ActiveStorage::PurgeJob < ActiveJob::Base + # FIXME: Limit this to a custom ActiveStorage error retry_on StandardError def perform(attachment_or_blob) diff --git a/lib/active_storage/railtie.rb b/lib/active_storage/railtie.rb new file mode 100644 index 0000000000..bf38d5aff5 --- /dev/null +++ b/lib/active_storage/railtie.rb @@ -0,0 +1,27 @@ +require "rails/railtie" + +module ActiveStorage + class Railtie < Rails::Railtie # :nodoc: + config.active_storage = ActiveSupport::OrderedOptions.new + + config.eager_load_namespaces << ActiveStorage + + initializer "active_storage.routes" do + require "active_storage/disk_controller" + + config.after_initialize do |app| + app.routes.prepend do + get "/rails/blobs/:encoded_key" => "active_storage/disk#show", as: :rails_disk_blob + end + end + end + + initializer "active_storage.attached" do + require "active_storage/attached" + + ActiveSupport.on_load(:active_record) do + extend ActiveStorage::Attached::Macros + end + end + end +end diff --git a/lib/active_vault/site.rb b/lib/active_storage/site.rb similarity index 80% rename from lib/active_vault/site.rb rename to lib/active_storage/site.rb index 29eddf1566..b3b0221c63 100644 --- a/lib/active_vault/site.rb +++ b/lib/active_storage/site.rb @@ -1,9 +1,9 @@ # Abstract class serving as an interface for concrete sites. -class ActiveVault::Site +class ActiveStorage::Site def self.configure(site, **options) begin - require "active_vault/site/#{site.to_s.downcase}_site" - ActiveVault::Site.const_get(:"#{site}Site").new(**options) + require "active_storage/site/#{site.to_s.downcase}_site" + ActiveStorage::Site.const_get(:"#{site}Site").new(**options) rescue LoadError => e puts "Couldn't configure site: #{site} (#{e.message})" end diff --git a/lib/active_vault/site/disk_site.rb b/lib/active_storage/site/disk_site.rb similarity index 89% rename from lib/active_vault/site/disk_site.rb rename to lib/active_storage/site/disk_site.rb index 73f86bac6a..2ff0b22fae 100644 --- a/lib/active_vault/site/disk_site.rb +++ b/lib/active_storage/site/disk_site.rb @@ -1,7 +1,7 @@ require "fileutils" require "pathname" -class ActiveVault::Site::DiskSite < ActiveVault::Site +class ActiveStorage::Site::DiskSite < ActiveStorage::Site attr_reader :root def initialize(root:) @@ -38,7 +38,7 @@ def exist?(key) def url(key, expires_in:, disposition:, filename:) - verified_key_with_expiration = ActiveVault::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) + verified_key_with_expiration = ActiveStorage::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) if defined?(Rails) && defined?(Rails.application) Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition) diff --git a/lib/active_vault/site/gcs_site.rb b/lib/active_storage/site/gcs_site.rb similarity index 94% rename from lib/active_vault/site/gcs_site.rb rename to lib/active_storage/site/gcs_site.rb index 8f51d486ec..bf681ca6a2 100644 --- a/lib/active_vault/site/gcs_site.rb +++ b/lib/active_storage/site/gcs_site.rb @@ -1,7 +1,7 @@ require "google/cloud/storage" require "active_support/core_ext/object/to_query" -class ActiveVault::Site::GCSSite < ActiveVault::Site +class ActiveStorage::Site::GCSSite < ActiveStorage::Site attr_reader :client, :bucket def initialize(project:, keyfile:, bucket:) diff --git a/lib/active_vault/site/mirror_site.rb b/lib/active_storage/site/mirror_site.rb similarity index 93% rename from lib/active_vault/site/mirror_site.rb rename to lib/active_storage/site/mirror_site.rb index 8a2fa52fcb..ba3ef0ef0e 100644 --- a/lib/active_vault/site/mirror_site.rb +++ b/lib/active_storage/site/mirror_site.rb @@ -1,4 +1,4 @@ -class ActiveVault::Site::MirrorSite < ActiveVault::Site +class ActiveStorage::Site::MirrorSite < ActiveStorage::Site attr_reader :sites def initialize(sites:) diff --git a/lib/active_vault/site/s3_site.rb b/lib/active_storage/site/s3_site.rb similarity index 96% rename from lib/active_vault/site/s3_site.rb rename to lib/active_storage/site/s3_site.rb index 49a7522170..65dad37cfe 100644 --- a/lib/active_vault/site/s3_site.rb +++ b/lib/active_storage/site/s3_site.rb @@ -1,6 +1,6 @@ require "aws-sdk" -class ActiveVault::Site::S3Site < ActiveVault::Site +class ActiveStorage::Site::S3Site < ActiveStorage::Site attr_reader :client, :bucket def initialize(access_key_id:, secret_access_key:, region:, bucket:) diff --git a/lib/active_vault/verified_key_with_expiration.rb b/lib/active_storage/verified_key_with_expiration.rb similarity index 84% rename from lib/active_vault/verified_key_with_expiration.rb rename to lib/active_storage/verified_key_with_expiration.rb index 95d4993ff0..8708106735 100644 --- a/lib/active_vault/verified_key_with_expiration.rb +++ b/lib/active_storage/verified_key_with_expiration.rb @@ -1,5 +1,5 @@ -class ActiveVault::VerifiedKeyWithExpiration - class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveVault') : nil +class ActiveStorage::VerifiedKeyWithExpiration + class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveStorage') : nil class << self def encode(key, expires_in: nil) diff --git a/lib/active_vault/attached/macros.rb b/lib/active_vault/attached/macros.rb deleted file mode 100644 index 1b95c14c9c..0000000000 --- a/lib/active_vault/attached/macros.rb +++ /dev/null @@ -1,23 +0,0 @@ -module ActiveVault::Attached::Macros - def has_one_attached(name, dependent: :purge_later) - define_method(name) do - instance_variable_get("@active_vault_attached_#{name}") || - instance_variable_set("@active_vault_attached_#{name}", ActiveVault::Attached::One.new(name, self)) - end - - if dependent == :purge_later - before_destroy { public_send(name).purge_later } - end - end - - def has_many_attached(name, dependent: :purge_later) - define_method(name) do - instance_variable_get("@active_vault_attached_#{name}") || - instance_variable_set("@active_vault_attached_#{name}", ActiveVault::Attached::Many.new(name, self)) - end - - if dependent == :purge_later - before_destroy { public_send(name).purge_later } - end - end -end diff --git a/lib/active_vault/railtie.rb b/lib/active_vault/railtie.rb deleted file mode 100644 index 1830780001..0000000000 --- a/lib/active_vault/railtie.rb +++ /dev/null @@ -1,27 +0,0 @@ -require "rails/railtie" - -module ActiveVault - class Railtie < Rails::Railtie # :nodoc: - config.active_vault = ActiveSupport::OrderedOptions.new - - config.eager_load_namespaces << ActiveVault - - initializer "active_vault.routes" do - require "active_vault/disk_controller" - - config.after_initialize do |app| - app.routes.prepend do - get "/rails/blobs/:encoded_key" => "active_vault/disk#show", as: :rails_disk_blob - end - end - end - - initializer "active_vault.attached" do - require "active_vault/attached" - - ActiveSupport.on_load(:active_record) do - extend ActiveVault::Attached::Macros - end - end - end -end diff --git a/test/attachments_test.rb b/test/attachments_test.rb index 1b784b50c1..6e25002bb1 100644 --- a/test/attachments_test.rb +++ b/test/attachments_test.rb @@ -1,6 +1,6 @@ require "test_helper" require "database/setup" -require "active_vault/blob" +require "active_storage/blob" require "active_job" ActiveJob::Base.queue_adapter = :test @@ -13,12 +13,12 @@ class User < ActiveRecord::Base has_many_attached :highlights end -class ActiveVault::AttachmentsTest < ActiveSupport::TestCase +class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase include ActiveJob::TestHelper setup { @user = User.create!(name: "DHH") } - teardown { ActiveVault::Blob.all.each(&:purge) } + teardown { ActiveStorage::Blob.all.each(&:purge) } test "attach existing blob" do @user.avatar.attach create_blob(filename: "funky.jpg") @@ -36,7 +36,7 @@ class ActiveVault::AttachmentsTest < ActiveSupport::TestCase @user.avatar.purge assert_not @user.avatar.attached? - assert_not ActiveVault::Blob.site.exist?(avatar_key) + assert_not ActiveStorage::Blob.site.exist?(avatar_key) end test "purge attached blob later when the record is destroyed" do @@ -46,8 +46,8 @@ class ActiveVault::AttachmentsTest < ActiveSupport::TestCase perform_enqueued_jobs do @user.destroy - assert_nil ActiveVault::Blob.find_by(key: avatar_key) - assert_not ActiveVault::Blob.site.exist?(avatar_key) + assert_nil ActiveStorage::Blob.find_by(key: avatar_key) + assert_not ActiveStorage::Blob.site.exist?(avatar_key) end end @@ -74,8 +74,8 @@ class ActiveVault::AttachmentsTest < ActiveSupport::TestCase @user.highlights.purge assert_not @user.highlights.attached? - assert_not ActiveVault::Blob.site.exist?(highlight_keys.first) - assert_not ActiveVault::Blob.site.exist?(highlight_keys.second) + assert_not ActiveStorage::Blob.site.exist?(highlight_keys.first) + assert_not ActiveStorage::Blob.site.exist?(highlight_keys.second) end test "purge attached blobs later when the record is destroyed" do @@ -85,11 +85,11 @@ class ActiveVault::AttachmentsTest < ActiveSupport::TestCase perform_enqueued_jobs do @user.destroy - assert_nil ActiveVault::Blob.find_by(key: highlight_keys.first) - assert_not ActiveVault::Blob.site.exist?(highlight_keys.first) + assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.first) + assert_not ActiveStorage::Blob.site.exist?(highlight_keys.first) - assert_nil ActiveVault::Blob.find_by(key: highlight_keys.second) - assert_not ActiveVault::Blob.site.exist?(highlight_keys.second) + assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.second) + assert_not ActiveStorage::Blob.site.exist?(highlight_keys.second) end end end diff --git a/test/blob_test.rb b/test/blob_test.rb index c7b4aeed39..880c656ecc 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -1,8 +1,8 @@ require "test_helper" require "database/setup" -require "active_vault/blob" +require "active_storage/blob" -class ActiveVault::BlobTest < ActiveSupport::TestCase +class ActiveStorage::BlobTest < ActiveSupport::TestCase test "create after upload sets byte size and checksum" do data = "Hello world!" blob = create_blob data: data @@ -23,6 +23,6 @@ class ActiveVault::BlobTest < ActiveSupport::TestCase private def expected_url_for(blob, disposition: :inline) - "/rails/blobs/#{ActiveVault::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}?disposition=#{disposition}" + "/rails/blobs/#{ActiveStorage::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}?disposition=#{disposition}" end end diff --git a/test/database/create_users_migration.rb b/test/database/create_users_migration.rb index 38dcdc129b..15be1938a9 100644 --- a/test/database/create_users_migration.rb +++ b/test/database/create_users_migration.rb @@ -1,4 +1,4 @@ -class ActiveVault::CreateUsers < ActiveRecord::Migration[5.1] +class ActiveStorage::CreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| t.string :name diff --git a/test/database/setup.rb b/test/database/setup.rb index 7373d72237..828edd86dd 100644 --- a/test/database/setup.rb +++ b/test/database/setup.rb @@ -1,6 +1,6 @@ -require "active_vault/migration" +require "active_storage/migration" require_relative "create_users_migration" ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') -ActiveVault::CreateTables.migrate(:up) -ActiveVault::CreateUsers.migrate(:up) +ActiveStorage::CreateTables.migrate(:up) +ActiveStorage::CreateUsers.migrate(:up) diff --git a/test/disk_controller_test.rb b/test/disk_controller_test.rb index eaf0b497ac..3d7f4ba6bd 100644 --- a/test/disk_controller_test.rb +++ b/test/disk_controller_test.rb @@ -4,30 +4,30 @@ require "action_controller" require "action_controller/test_case" -require "active_vault/disk_controller" -require "active_vault/verified_key_with_expiration" +require "active_storage/disk_controller" +require "active_storage/verified_key_with_expiration" -class ActiveVault::DiskControllerTest < ActionController::TestCase +class ActiveStorage::DiskControllerTest < ActionController::TestCase Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| routes.draw do - get "/rails/blobs/:encoded_key" => "active_vault/disk#show", as: :rails_disk_blob + get "/rails/blobs/:encoded_key" => "active_storage/disk#show", as: :rails_disk_blob end end setup do @blob = create_blob @routes = Routes - @controller = ActiveVault::DiskController.new + @controller = ActiveStorage::DiskController.new end test "showing blob inline" do - get :show, params: { encoded_key: ActiveVault::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes) } + get :show, params: { encoded_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes) } assert_equal "inline; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end test "sending blob as attachment" do - get :show, params: { encoded_key: ActiveVault::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), disposition: :attachment } + get :show, params: { encoded_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), disposition: :attachment } assert_equal "attachment; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end diff --git a/test/filename_test.rb b/test/filename_test.rb index 5cb67016c0..448ba7f766 100644 --- a/test/filename_test.rb +++ b/test/filename_test.rb @@ -1,9 +1,9 @@ require "test_helper" -class ActiveVault::FilenameTest < ActiveSupport::TestCase +class ActiveStorage::FilenameTest < ActiveSupport::TestCase test "sanitize" do "%$|:;/\t\r\n\\".each_char do |character| - filename = ActiveVault::Filename.new("foo#{character}bar.pdf") + filename = ActiveStorage::Filename.new("foo#{character}bar.pdf") assert_equal 'foo-bar.pdf', filename.sanitized assert_equal 'foo-bar.pdf', filename.to_s end @@ -16,21 +16,21 @@ class ActiveVault::FilenameTest < ActiveSupport::TestCase "\xCF" => "�", "\x00" => "", }.each do |actual, expected| - assert_equal expected, ActiveVault::Filename.new(actual).sanitized + assert_equal expected, ActiveStorage::Filename.new(actual).sanitized end end test "strips RTL override chars used to spoof unsafe executables as docs" do # Would be displayed in Windows as "evilexe.pdf" due to the right-to-left # (RTL) override char! - assert_equal 'evil-fdp.exe', ActiveVault::Filename.new("evil\u{202E}fdp.exe").sanitized + assert_equal 'evil-fdp.exe', ActiveStorage::Filename.new("evil\u{202E}fdp.exe").sanitized end test "compare case-insensitively" do - assert_operator ActiveVault::Filename.new('foobar.pdf'), :==, ActiveVault::Filename.new('FooBar.PDF') + assert_operator ActiveStorage::Filename.new('foobar.pdf'), :==, ActiveStorage::Filename.new('FooBar.PDF') end test "compare sanitized" do - assert_operator ActiveVault::Filename.new('foo-bar.pdf'), :==, ActiveVault::Filename.new("foo\tbar.pdf") + assert_operator ActiveStorage::Filename.new('foo-bar.pdf'), :==, ActiveStorage::Filename.new("foo\tbar.pdf") end end diff --git a/test/site/disk_site_test.rb b/test/site/disk_site_test.rb index e9ebdcb0be..a04414ea68 100644 --- a/test/site/disk_site_test.rb +++ b/test/site/disk_site_test.rb @@ -1,8 +1,8 @@ require "tmpdir" require "site/shared_site_tests" -class ActiveVault::Site::DiskSiteTest < ActiveSupport::TestCase - SITE = ActiveVault::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_vault")) +class ActiveStorage::Site::DiskSiteTest < ActiveSupport::TestCase + SITE = ActiveStorage::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) - include ActiveVault::Site::SharedSiteTests + include ActiveStorage::Site::SharedSiteTests end diff --git a/test/site/gcs_site_test.rb b/test/site/gcs_site_test.rb index b3712a2116..98b1a3d767 100644 --- a/test/site/gcs_site_test.rb +++ b/test/site/gcs_site_test.rb @@ -1,10 +1,10 @@ require "site/shared_site_tests" if SITE_CONFIGURATIONS[:gcs] - class ActiveVault::Site::GCSSiteTest < ActiveSupport::TestCase - SITE = ActiveVault::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) + class ActiveStorage::Site::GCSSiteTest < ActiveSupport::TestCase + SITE = ActiveStorage::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) - include ActiveVault::Site::SharedSiteTests + include ActiveStorage::Site::SharedSiteTests test "signed URL generation" do travel_to Time.now do diff --git a/test/site/mirror_site_test.rb b/test/site/mirror_site_test.rb index bdb0b4c357..7ced377cde 100644 --- a/test/site/mirror_site_test.rb +++ b/test/site/mirror_site_test.rb @@ -1,15 +1,15 @@ require "tmpdir" require "site/shared_site_tests" -class ActiveVault::Site::MirrorSiteTest < ActiveSupport::TestCase - PRIMARY_DISK_SITE = ActiveVault::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_vault")) - SECONDARY_DISK_SITE = ActiveVault::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_vault_mirror")) +class ActiveStorage::Site::MirrorSiteTest < ActiveSupport::TestCase + PRIMARY_DISK_SITE = ActiveStorage::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) + SECONDARY_DISK_SITE = ActiveStorage::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage_mirror")) - SITE = ActiveVault::Site.configure :Mirror, sites: [ PRIMARY_DISK_SITE, SECONDARY_DISK_SITE ] + SITE = ActiveStorage::Site.configure :Mirror, sites: [ PRIMARY_DISK_SITE, SECONDARY_DISK_SITE ] - include ActiveVault::Site::SharedSiteTests + include ActiveStorage::Site::SharedSiteTests - test "uploading to all sites" do + test "uploading was done to all sites" do begin key = SecureRandom.base58(24) data = "Something else entirely!" @@ -27,11 +27,4 @@ class ActiveVault::Site::MirrorSiteTest < ActiveSupport::TestCase assert PRIMARY_DISK_SITE.exist?(FIXTURE_KEY) assert SECONDARY_DISK_SITE.exist?(FIXTURE_KEY) end - - test "URL generation for primary site" do - travel_to Time.now do - assert_equal PRIMARY_DISK_SITE.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "test.txt"), - SITE.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "test.txt") - end - end end diff --git a/test/site/s3_site_test.rb b/test/site/s3_site_test.rb index 6daeaac2ea..a9cb6ca618 100644 --- a/test/site/s3_site_test.rb +++ b/test/site/s3_site_test.rb @@ -1,10 +1,10 @@ require "site/shared_site_tests" if SITE_CONFIGURATIONS[:s3] - class ActiveVault::Site::S3SiteTest < ActiveSupport::TestCase - SITE = ActiveVault::Site.configure(:S3, SITE_CONFIGURATIONS[:s3]) + class ActiveStorage::Site::S3SiteTest < ActiveSupport::TestCase + SITE = ActiveStorage::Site.configure(:S3, SITE_CONFIGURATIONS[:s3]) - include ActiveVault::Site::SharedSiteTests + include ActiveStorage::Site::SharedSiteTests end else puts "Skipping S3 Site tests because no S3 configuration was supplied" diff --git a/test/site/shared_site_tests.rb b/test/site/shared_site_tests.rb index 56f1a13742..687c35e941 100644 --- a/test/site/shared_site_tests.rb +++ b/test/site/shared_site_tests.rb @@ -8,7 +8,7 @@ puts "Missing site configuration file in test/sites/configurations.yml" end -module ActiveVault::Site::SharedSiteTests +module ActiveStorage::Site::SharedSiteTests extend ActiveSupport::Concern FIXTURE_KEY = SecureRandom.base58(24) diff --git a/test/test_helper.rb b/test/test_helper.rb index b18613c365..f354a995e4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,25 +4,25 @@ require "active_support/testing/autorun" require "byebug" -require "active_vault" +require "active_storage" -require "active_vault/site" -ActiveVault::Blob.site = ActiveVault::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_vault")) +require "active_storage/site" +ActiveStorage::Blob.site = ActiveStorage::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) -require "active_vault/verified_key_with_expiration" -ActiveVault::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") +require "active_storage/verified_key_with_expiration" +ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") class ActiveSupport::TestCase private def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") - ActiveVault::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type + ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type end end -require "active_vault/attached" -ActiveRecord::Base.send :extend, ActiveVault::Attached::Macros +require "active_storage/attached" +ActiveRecord::Base.send :extend, ActiveStorage::Attached::Macros require "global_id" -GlobalID.app = "ActiveVaultExampleApp" +GlobalID.app = "ActiveStorageExampleApp" ActiveRecord::Base.send :include, GlobalID::Identification diff --git a/test/verified_key_with_expiration_test.rb b/test/verified_key_with_expiration_test.rb index 073bb047f6..ee4dc7e02e 100644 --- a/test/verified_key_with_expiration_test.rb +++ b/test/verified_key_with_expiration_test.rb @@ -1,19 +1,19 @@ require "test_helper" require "active_support/core_ext/securerandom" -class ActiveVault::VerifiedKeyWithExpirationTest < ActiveSupport::TestCase +class ActiveStorage::VerifiedKeyWithExpirationTest < ActiveSupport::TestCase FIXTURE_KEY = SecureRandom.base58(24) test "without expiration" do - encoded_key = ActiveVault::VerifiedKeyWithExpiration.encode(FIXTURE_KEY) - assert_equal FIXTURE_KEY, ActiveVault::VerifiedKeyWithExpiration.decode(encoded_key) + encoded_key = ActiveStorage::VerifiedKeyWithExpiration.encode(FIXTURE_KEY) + assert_equal FIXTURE_KEY, ActiveStorage::VerifiedKeyWithExpiration.decode(encoded_key) end test "with expiration" do - encoded_key = ActiveVault::VerifiedKeyWithExpiration.encode(FIXTURE_KEY, expires_in: 1.minute) - assert_equal FIXTURE_KEY, ActiveVault::VerifiedKeyWithExpiration.decode(encoded_key) + encoded_key = ActiveStorage::VerifiedKeyWithExpiration.encode(FIXTURE_KEY, expires_in: 1.minute) + assert_equal FIXTURE_KEY, ActiveStorage::VerifiedKeyWithExpiration.decode(encoded_key) travel 2.minutes - assert_nil ActiveVault::VerifiedKeyWithExpiration.decode(encoded_key) + assert_nil ActiveStorage::VerifiedKeyWithExpiration.decode(encoded_key) end end From b3a9f3556dedb80cfa6336e6241d933baeb4f906 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 11:34:21 +0200 Subject: [PATCH 091/289] Update README with new name --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8803cd9f3b..73602db219 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Active Vault +# Active Storage -Active Vault makes it simple to upload and reference files in cloud sites, like Amazon S3 or Google Cloud Storage, +Active Storage makes it simple to upload and reference files in cloud sites, like Amazon S3 or Google Cloud Storage, and attach those files to Active Records. It also provides a disk site for testing or local deployments, but the focus is on cloud storage. @@ -74,4 +74,4 @@ Add `require "active_storage"` to config/application.rb and create a `config/ini ## License -Active Vault is released under the [MIT License](https://opensource.org/licenses/MIT). +Active Storage is released under the [MIT License](https://opensource.org/licenses/MIT). From 35d5bddabcd8f0eccc7de3ddf60431ea196508a1 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 12:22:44 +0200 Subject: [PATCH 092/289] Rename from Site to Service now that we're called Active Storage --- README.md | 10 +-- lib/active_storage.rb | 2 +- lib/active_storage/blob.rb | 16 ++--- lib/active_storage/config/sites.yml | 12 ++-- lib/active_storage/{site.rb => service.rb} | 12 ++-- .../disk_site.rb => service/disk_service.rb} | 2 +- .../gcs_site.rb => service/gcs_service.rb} | 2 +- lib/active_storage/service/mirror_service.rb | 51 +++++++++++++++ .../s3_site.rb => service/s3_service.rb} | 2 +- lib/active_storage/site/mirror_site.rb | 51 --------------- test/attachments_test.rb | 12 ++-- test/{site => service}/.gitignore | 0 .../configurations-example.yml | 0 test/service/disk_service_test.rb | 8 +++ test/service/gcs_service_test.rb | 20 ++++++ test/service/mirror_service_test.rb | 30 +++++++++ test/service/s3_service_test.rb | 11 ++++ test/service/shared_service_tests.rb | 63 +++++++++++++++++++ test/site/disk_site_test.rb | 8 --- test/site/gcs_site_test.rb | 20 ------ test/site/mirror_site_test.rb | 30 --------- test/site/s3_site_test.rb | 11 ---- test/site/shared_site_tests.rb | 63 ------------------- test/test_helper.rb | 4 +- 24 files changed, 220 insertions(+), 220 deletions(-) rename lib/active_storage/{site.rb => service.rb} (58%) rename lib/active_storage/{site/disk_site.rb => service/disk_service.rb} (95%) rename lib/active_storage/{site/gcs_site.rb => service/gcs_service.rb} (93%) create mode 100644 lib/active_storage/service/mirror_service.rb rename lib/active_storage/{site/s3_site.rb => service/s3_service.rb} (95%) delete mode 100644 lib/active_storage/site/mirror_site.rb rename test/{site => service}/.gitignore (100%) rename test/{site => service}/configurations-example.yml (100%) create mode 100644 test/service/disk_service_test.rb create mode 100644 test/service/gcs_service_test.rb create mode 100644 test/service/mirror_service_test.rb create mode 100644 test/service/s3_service_test.rb create mode 100644 test/service/shared_service_tests.rb delete mode 100644 test/site/disk_site_test.rb delete mode 100644 test/site/gcs_site_test.rb delete mode 100644 test/site/mirror_site_test.rb delete mode 100644 test/site/s3_site_test.rb delete mode 100644 test/site/shared_site_tests.rb diff --git a/README.md b/README.md index 73602db219..b768520756 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Active Storage -Active Storage makes it simple to upload and reference files in cloud sites, like Amazon S3 or Google Cloud Storage, -and attach those files to Active Records. It also provides a disk site for testing or local deployments, but the +Active Storage makes it simple to upload and reference files in cloud services, like Amazon S3 or Google Cloud Storage, +and attach those files to Active Records. It also provides a disk service for testing or local deployments, but the focus is on cloud storage. ## Example @@ -55,7 +55,7 @@ end ## Configuration -Add `require "active_storage"` to config/application.rb and create a `config/initializers/active_storage_sites.rb` with the following: +Add `require "active_storage"` to config/application.rb and create a `config/initializers/active_storage_services.rb` with the following: ```ruby @@ -65,10 +65,10 @@ Add `require "active_storage"` to config/application.rb and create a `config/ini - Strip Download of its resposibilities and delete class - Proper logging -- Convert MirrorSite to use threading +- Convert MirrorService to use threading - Read metadata via Marcel? - Copy over migration to app via rake task -- Add Migrator to copy/move between sites +- Add Migrator to copy/move between services - Explore direct uploads to cloud - Extract VerifiedKeyWithExpiration into Rails as a feature of MessageVerifier diff --git a/lib/active_storage.rb b/lib/active_storage.rb index e87eb8a506..f72fe0d017 100644 --- a/lib/active_storage.rb +++ b/lib/active_storage.rb @@ -5,5 +5,5 @@ module ActiveStorage extend ActiveSupport::Autoload autoload :Blob - autoload :Site + autoload :Service end diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb index edf57b5c78..4ce344e2a1 100644 --- a/lib/active_storage/blob.rb +++ b/lib/active_storage/blob.rb @@ -1,4 +1,4 @@ -require "active_storage/site" +require "active_storage/service" require "active_storage/filename" require "active_storage/purge_job" @@ -9,7 +9,7 @@ class ActiveStorage::Blob < ActiveRecord::Base has_secure_token :key store :metadata, coder: JSON - class_attribute :site + class_attribute :service class << self def build_after_upload(io:, filename:, content_type: nil, metadata: nil) @@ -37,24 +37,24 @@ def filename end def url(expires_in: 5.minutes, disposition: :inline) - site.url key, expires_in: expires_in, disposition: disposition, filename: filename + service.url key, expires_in: expires_in, disposition: disposition, filename: filename end def upload(io) - site.upload(key, io) + service.upload(key, io) - self.checksum = site.checksum(key) - self.byte_size = site.byte_size(key) + self.checksum = service.checksum(key) + self.byte_size = service.byte_size(key) end def download - site.download key + service.download key end def delete - site.delete key + service.delete key end def purge diff --git a/lib/active_storage/config/sites.yml b/lib/active_storage/config/sites.yml index 43bc77fbf9..317ef2b9b7 100644 --- a/lib/active_storage/config/sites.yml +++ b/lib/active_storage/config/sites.yml @@ -1,25 +1,25 @@ # Configuration should be something like this: # # config/environments/development.rb -# config.active_storage.site = :local +# config.active_storage.service = :local # # config/environments/production.rb -# config.active_storage.site = :amazon +# config.active_storage.service = :amazon local: - site: Disk + service: Disk root: <%%= File.join(Dir.tmpdir, "active_storage") %> amazon: - site: S3 + service: S3 access_key_id: <%%= Rails.application.secrets.aws[:access_key_id] %> secret_access_key: <%%= Rails.application.secrets.aws[:secret_access_key] %> region: us-east-1 bucket: <%= Rails.application.class.name.remove(/::Application$/).underscore %> google: - site: GCS + service: GCS mirror: - site: Mirror + service: Mirror primary: amazon secondaries: google diff --git a/lib/active_storage/site.rb b/lib/active_storage/service.rb similarity index 58% rename from lib/active_storage/site.rb rename to lib/active_storage/service.rb index b3b0221c63..038b6ccb53 100644 --- a/lib/active_storage/site.rb +++ b/lib/active_storage/service.rb @@ -1,11 +1,11 @@ -# Abstract class serving as an interface for concrete sites. -class ActiveStorage::Site - def self.configure(site, **options) +# Abstract class serving as an interface for concrete services. +class ActiveStorage::Service + def self.configure(service, **options) begin - require "active_storage/site/#{site.to_s.downcase}_site" - ActiveStorage::Site.const_get(:"#{site}Site").new(**options) + require "active_storage/service/#{service.to_s.downcase}_service" + ActiveStorage::Service.const_get(:"#{service}Service").new(**options) rescue LoadError => e - puts "Couldn't configure site: #{site} (#{e.message})" + puts "Couldn't configure service: #{service} (#{e.message})" end end diff --git a/lib/active_storage/site/disk_site.rb b/lib/active_storage/service/disk_service.rb similarity index 95% rename from lib/active_storage/site/disk_site.rb rename to lib/active_storage/service/disk_service.rb index 2ff0b22fae..6977b5b82e 100644 --- a/lib/active_storage/site/disk_site.rb +++ b/lib/active_storage/service/disk_service.rb @@ -1,7 +1,7 @@ require "fileutils" require "pathname" -class ActiveStorage::Site::DiskSite < ActiveStorage::Site +class ActiveStorage::Service::DiskService < ActiveStorage::Service attr_reader :root def initialize(root:) diff --git a/lib/active_storage/site/gcs_site.rb b/lib/active_storage/service/gcs_service.rb similarity index 93% rename from lib/active_storage/site/gcs_site.rb rename to lib/active_storage/service/gcs_service.rb index bf681ca6a2..18ec1de133 100644 --- a/lib/active_storage/site/gcs_site.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -1,7 +1,7 @@ require "google/cloud/storage" require "active_support/core_ext/object/to_query" -class ActiveStorage::Site::GCSSite < ActiveStorage::Site +class ActiveStorage::Service::GCSService < ActiveStorage::Service attr_reader :client, :bucket def initialize(project:, keyfile:, bucket:) diff --git a/lib/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb new file mode 100644 index 0000000000..2a3518e59e --- /dev/null +++ b/lib/active_storage/service/mirror_service.rb @@ -0,0 +1,51 @@ +class ActiveStorage::Service::MirrorService < ActiveStorage::Service + attr_reader :services + + def initialize(services:) + @services = services + end + + def upload(key, io) + services.collect do |service| + service.upload key, io + io.rewind + end + end + + def download(key) + services.detect { |service| service.exist?(key) }.download(key) + end + + def delete(key) + perform_across_services :delete, key + end + + def exist?(key) + perform_across_services(:exist?, key).any? + end + + + def url(key, **options) + primary_service.url(key, **options) + end + + def byte_size(key) + primary_service.byte_size(key) + end + + def checksum(key) + primary_service.checksum(key) + end + + private + def primary_service + services.first + end + + def perform_across_services(method, *args) + # FIXME: Convert to be threaded + services.collect do |service| + service.public_send method, *args + end + end +end diff --git a/lib/active_storage/site/s3_site.rb b/lib/active_storage/service/s3_service.rb similarity index 95% rename from lib/active_storage/site/s3_site.rb rename to lib/active_storage/service/s3_service.rb index 65dad37cfe..811321a172 100644 --- a/lib/active_storage/site/s3_site.rb +++ b/lib/active_storage/service/s3_service.rb @@ -1,6 +1,6 @@ require "aws-sdk" -class ActiveStorage::Site::S3Site < ActiveStorage::Site +class ActiveStorage::Service::S3Service < ActiveStorage::Service attr_reader :client, :bucket def initialize(access_key_id:, secret_access_key:, region:, bucket:) diff --git a/lib/active_storage/site/mirror_site.rb b/lib/active_storage/site/mirror_site.rb deleted file mode 100644 index ba3ef0ef0e..0000000000 --- a/lib/active_storage/site/mirror_site.rb +++ /dev/null @@ -1,51 +0,0 @@ -class ActiveStorage::Site::MirrorSite < ActiveStorage::Site - attr_reader :sites - - def initialize(sites:) - @sites = sites - end - - def upload(key, io) - sites.collect do |site| - site.upload key, io - io.rewind - end - end - - def download(key) - sites.detect { |site| site.exist?(key) }.download(key) - end - - def delete(key) - perform_across_sites :delete, key - end - - def exist?(key) - perform_across_sites(:exist?, key).any? - end - - - def url(key, **options) - primary_site.url(key, **options) - end - - def byte_size(key) - primary_site.byte_size(key) - end - - def checksum(key) - primary_site.checksum(key) - end - - private - def primary_site - sites.first - end - - def perform_across_sites(method, *args) - # FIXME: Convert to be threaded - sites.collect do |site| - site.public_send method, *args - end - end -end diff --git a/test/attachments_test.rb b/test/attachments_test.rb index 6e25002bb1..33bbff716d 100644 --- a/test/attachments_test.rb +++ b/test/attachments_test.rb @@ -36,7 +36,7 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase @user.avatar.purge assert_not @user.avatar.attached? - assert_not ActiveStorage::Blob.site.exist?(avatar_key) + assert_not ActiveStorage::Blob.service.exist?(avatar_key) end test "purge attached blob later when the record is destroyed" do @@ -47,7 +47,7 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase @user.destroy assert_nil ActiveStorage::Blob.find_by(key: avatar_key) - assert_not ActiveStorage::Blob.site.exist?(avatar_key) + assert_not ActiveStorage::Blob.service.exist?(avatar_key) end end @@ -74,8 +74,8 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase @user.highlights.purge assert_not @user.highlights.attached? - assert_not ActiveStorage::Blob.site.exist?(highlight_keys.first) - assert_not ActiveStorage::Blob.site.exist?(highlight_keys.second) + assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first) + assert_not ActiveStorage::Blob.service.exist?(highlight_keys.second) end test "purge attached blobs later when the record is destroyed" do @@ -86,10 +86,10 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase @user.destroy assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.first) - assert_not ActiveStorage::Blob.site.exist?(highlight_keys.first) + assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first) assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.second) - assert_not ActiveStorage::Blob.site.exist?(highlight_keys.second) + assert_not ActiveStorage::Blob.service.exist?(highlight_keys.second) end end end diff --git a/test/site/.gitignore b/test/service/.gitignore similarity index 100% rename from test/site/.gitignore rename to test/service/.gitignore diff --git a/test/site/configurations-example.yml b/test/service/configurations-example.yml similarity index 100% rename from test/site/configurations-example.yml rename to test/service/configurations-example.yml diff --git a/test/service/disk_service_test.rb b/test/service/disk_service_test.rb new file mode 100644 index 0000000000..5dd7cff303 --- /dev/null +++ b/test/service/disk_service_test.rb @@ -0,0 +1,8 @@ +require "tmpdir" +require "service/shared_service_tests" + +class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase + SERVICE = ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) + + include ActiveStorage::Service::SharedServiceTests +end diff --git a/test/service/gcs_service_test.rb b/test/service/gcs_service_test.rb new file mode 100644 index 0000000000..42f9cd3061 --- /dev/null +++ b/test/service/gcs_service_test.rb @@ -0,0 +1,20 @@ +require "service/shared_service_tests" + +if SERVICE_CONFIGURATIONS[:gcs] + class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase + SERVICE = ActiveStorage::Service.configure(:GCS, SERVICE_CONFIGURATIONS[:gcs]) + + include ActiveStorage::Service::SharedServiceTests + + test "signed URL generation" do + travel_to Time.now do + url = SERVICE.bucket.signed_url(FIXTURE_KEY, expires: 120) + + "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" + + assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") + end + end + end +else + puts "Skipping GCS Service tests because no GCS configuration was supplied" +end diff --git a/test/service/mirror_service_test.rb b/test/service/mirror_service_test.rb new file mode 100644 index 0000000000..3b22c4f049 --- /dev/null +++ b/test/service/mirror_service_test.rb @@ -0,0 +1,30 @@ +require "tmpdir" +require "service/shared_service_tests" + +class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase + PRIMARY_DISK_SERVICE = ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) + SECONDARY_DISK_SERVICE = ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage_mirror")) + + SERVICE = ActiveStorage::Service.configure :Mirror, services: [ PRIMARY_DISK_SERVICE, SECONDARY_DISK_SERVICE ] + + include ActiveStorage::Service::SharedServiceTests + + test "uploading was done to all services" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + io = StringIO.new(data) + @service.upload(key, io) + + assert_equal data, PRIMARY_DISK_SERVICE.download(key) + assert_equal data, SECONDARY_DISK_SERVICE.download(key) + ensure + @service.delete key + end + end + + test "existing in all services" do + assert PRIMARY_DISK_SERVICE.exist?(FIXTURE_KEY) + assert SECONDARY_DISK_SERVICE.exist?(FIXTURE_KEY) + end +end diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb new file mode 100644 index 0000000000..604dfd6c60 --- /dev/null +++ b/test/service/s3_service_test.rb @@ -0,0 +1,11 @@ +require "service/shared_service_tests" + +if SERVICE_CONFIGURATIONS[:s3] + class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase + SERVICE = ActiveStorage::Service.configure(:S3, SERVICE_CONFIGURATIONS[:s3]) + + include ActiveStorage::Service::SharedServiceTests + end +else + puts "Skipping S3 Service tests because no S3 configuration was supplied" +end diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb new file mode 100644 index 0000000000..16672ab49b --- /dev/null +++ b/test/service/shared_service_tests.rb @@ -0,0 +1,63 @@ +require "test_helper" +require "active_support/core_ext/securerandom" +require "yaml" + +SERVICE_CONFIGURATIONS = begin + YAML.load_file(File.expand_path("../configurations.yml", __FILE__)).deep_symbolize_keys +rescue Errno::ENOENT + puts "Missing service configuration file in test/services/configurations.yml" +end + +module ActiveStorage::Service::SharedServiceTests + extend ActiveSupport::Concern + + FIXTURE_KEY = SecureRandom.base58(24) + FIXTURE_FILE = StringIO.new("Hello world!") + + included do + setup do + @service = self.class.const_get(:SERVICE) + @service.upload FIXTURE_KEY, FIXTURE_FILE + FIXTURE_FILE.rewind + end + + teardown do + @service.delete FIXTURE_KEY + FIXTURE_FILE.rewind + end + + test "uploading" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + @service.upload(key, StringIO.new(data)) + + assert_equal data, @service.download(key) + ensure + @service.delete key + end + end + + test "downloading" do + assert_equal FIXTURE_FILE.read, @service.download(FIXTURE_KEY) + end + + test "existing" do + assert @service.exist?(FIXTURE_KEY) + assert_not @service.exist?(FIXTURE_KEY + "nonsense") + end + + test "deleting" do + @service.delete FIXTURE_KEY + assert_not @service.exist?(FIXTURE_KEY) + end + + test "sizing" do + assert_equal FIXTURE_FILE.size, @service.byte_size(FIXTURE_KEY) + end + + test "checksumming" do + assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @service.checksum(FIXTURE_KEY) + end + end +end diff --git a/test/site/disk_site_test.rb b/test/site/disk_site_test.rb deleted file mode 100644 index a04414ea68..0000000000 --- a/test/site/disk_site_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "tmpdir" -require "site/shared_site_tests" - -class ActiveStorage::Site::DiskSiteTest < ActiveSupport::TestCase - SITE = ActiveStorage::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) - - include ActiveStorage::Site::SharedSiteTests -end diff --git a/test/site/gcs_site_test.rb b/test/site/gcs_site_test.rb deleted file mode 100644 index 98b1a3d767..0000000000 --- a/test/site/gcs_site_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require "site/shared_site_tests" - -if SITE_CONFIGURATIONS[:gcs] - class ActiveStorage::Site::GCSSiteTest < ActiveSupport::TestCase - SITE = ActiveStorage::Site.configure(:GCS, SITE_CONFIGURATIONS[:gcs]) - - include ActiveStorage::Site::SharedSiteTests - - test "signed URL generation" do - travel_to Time.now do - url = SITE.bucket.signed_url(FIXTURE_KEY, expires: 120) + - "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" - - assert_equal url, @site.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") - end - end - end -else - puts "Skipping GCS Site tests because no GCS configuration was supplied" -end diff --git a/test/site/mirror_site_test.rb b/test/site/mirror_site_test.rb deleted file mode 100644 index 7ced377cde..0000000000 --- a/test/site/mirror_site_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require "tmpdir" -require "site/shared_site_tests" - -class ActiveStorage::Site::MirrorSiteTest < ActiveSupport::TestCase - PRIMARY_DISK_SITE = ActiveStorage::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) - SECONDARY_DISK_SITE = ActiveStorage::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage_mirror")) - - SITE = ActiveStorage::Site.configure :Mirror, sites: [ PRIMARY_DISK_SITE, SECONDARY_DISK_SITE ] - - include ActiveStorage::Site::SharedSiteTests - - test "uploading was done to all sites" do - begin - key = SecureRandom.base58(24) - data = "Something else entirely!" - io = StringIO.new(data) - @site.upload(key, io) - - assert_equal data, PRIMARY_DISK_SITE.download(key) - assert_equal data, SECONDARY_DISK_SITE.download(key) - ensure - @site.delete key - end - end - - test "existing in all sites" do - assert PRIMARY_DISK_SITE.exist?(FIXTURE_KEY) - assert SECONDARY_DISK_SITE.exist?(FIXTURE_KEY) - end -end diff --git a/test/site/s3_site_test.rb b/test/site/s3_site_test.rb deleted file mode 100644 index a9cb6ca618..0000000000 --- a/test/site/s3_site_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "site/shared_site_tests" - -if SITE_CONFIGURATIONS[:s3] - class ActiveStorage::Site::S3SiteTest < ActiveSupport::TestCase - SITE = ActiveStorage::Site.configure(:S3, SITE_CONFIGURATIONS[:s3]) - - include ActiveStorage::Site::SharedSiteTests - end -else - puts "Skipping S3 Site tests because no S3 configuration was supplied" -end diff --git a/test/site/shared_site_tests.rb b/test/site/shared_site_tests.rb deleted file mode 100644 index 687c35e941..0000000000 --- a/test/site/shared_site_tests.rb +++ /dev/null @@ -1,63 +0,0 @@ -require "test_helper" -require "active_support/core_ext/securerandom" -require "yaml" - -SITE_CONFIGURATIONS = begin - YAML.load_file(File.expand_path("../configurations.yml", __FILE__)).deep_symbolize_keys -rescue Errno::ENOENT - puts "Missing site configuration file in test/sites/configurations.yml" -end - -module ActiveStorage::Site::SharedSiteTests - extend ActiveSupport::Concern - - FIXTURE_KEY = SecureRandom.base58(24) - FIXTURE_FILE = StringIO.new("Hello world!") - - included do - setup do - @site = self.class.const_get(:SITE) - @site.upload FIXTURE_KEY, FIXTURE_FILE - FIXTURE_FILE.rewind - end - - teardown do - @site.delete FIXTURE_KEY - FIXTURE_FILE.rewind - end - - test "uploading" do - begin - key = SecureRandom.base58(24) - data = "Something else entirely!" - @site.upload(key, StringIO.new(data)) - - assert_equal data, @site.download(key) - ensure - @site.delete key - end - end - - test "downloading" do - assert_equal FIXTURE_FILE.read, @site.download(FIXTURE_KEY) - end - - test "existing" do - assert @site.exist?(FIXTURE_KEY) - assert_not @site.exist?(FIXTURE_KEY + "nonsense") - end - - test "deleting" do - @site.delete FIXTURE_KEY - assert_not @site.exist?(FIXTURE_KEY) - end - - test "sizing" do - assert_equal FIXTURE_FILE.size, @site.byte_size(FIXTURE_KEY) - end - - test "checksumming" do - assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @site.checksum(FIXTURE_KEY) - end - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb index f354a995e4..dcabe33c18 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,8 +6,8 @@ require "active_storage" -require "active_storage/site" -ActiveStorage::Blob.site = ActiveStorage::Site.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) +require "active_storage/service" +ActiveStorage::Blob.service = ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) require "active_storage/verified_key_with_expiration" ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") From a8e849bb0d621f300fb5239699749e7b88e3cf03 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 6 Jul 2017 07:30:21 -0400 Subject: [PATCH 093/289] Mirror: only hit all sites for upload and delete The mirror service exists for the purpose of migration, where all blobs exist in the primary subservice and a subset of blobs exist in the secondary subservice. Since the primary subservice is the source of truth until a migration is completed, operations like existence checks need not be performed against the secondary subservices. --- lib/active_storage/service/mirror_service.rb | 25 ++++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/lib/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb index 2a3518e59e..0d37ad96a3 100644 --- a/lib/active_storage/service/mirror_service.rb +++ b/lib/active_storage/service/mirror_service.rb @@ -1,6 +1,10 @@ +require "active_support/core_ext/module/delegation" + class ActiveStorage::Service::MirrorService < ActiveStorage::Service attr_reader :services + delegate :download, :exist?, :url, :byte_size, :checksum, to: :primary_service + def initialize(services:) @services = services end @@ -12,31 +16,10 @@ def upload(key, io) end end - def download(key) - services.detect { |service| service.exist?(key) }.download(key) - end - def delete(key) perform_across_services :delete, key end - def exist?(key) - perform_across_services(:exist?, key).any? - end - - - def url(key, **options) - primary_service.url(key, **options) - end - - def byte_size(key) - primary_service.byte_size(key) - end - - def checksum(key) - primary_service.checksum(key) - end - private def primary_service services.first From 7341d9100985b59d14afb804c71826d6617bba7e Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 6 Jul 2017 07:53:26 -0400 Subject: [PATCH 094/289] Flesh out mirror tests --- test/service/mirror_service_test.rb | 33 +++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/test/service/mirror_service_test.rb b/test/service/mirror_service_test.rb index 3b22c4f049..95fe369a4c 100644 --- a/test/service/mirror_service_test.rb +++ b/test/service/mirror_service_test.rb @@ -9,12 +9,10 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase include ActiveStorage::Service::SharedServiceTests - test "uploading was done to all services" do + test "uploading to all services" do begin - key = SecureRandom.base58(24) data = "Something else entirely!" - io = StringIO.new(data) - @service.upload(key, io) + key = upload(data, to: @service) assert_equal data, PRIMARY_DISK_SERVICE.download(key) assert_equal data, SECONDARY_DISK_SERVICE.download(key) @@ -23,8 +21,29 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase end end - test "existing in all services" do - assert PRIMARY_DISK_SERVICE.exist?(FIXTURE_KEY) - assert SECONDARY_DISK_SERVICE.exist?(FIXTURE_KEY) + test "downloading from primary service" do + data = "Something else entirely!" + key = upload(data, to: PRIMARY_DISK_SERVICE) + + assert_equal data, @service.download(key) + end + + test "deleting from all services" do + @service.delete FIXTURE_KEY + assert_not PRIMARY_DISK_SERVICE.exist?(FIXTURE_KEY) + assert_not SECONDARY_DISK_SERVICE.exist?(FIXTURE_KEY) + end + + test "URL generation in primary service" do + travel_to Time.now do + assert_equal PRIMARY_DISK_SERVICE.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt"), + @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") + end + end + + def upload(data, to:) + SecureRandom.base58(24).tap do |key| + @service.upload key, StringIO.new(data) + end end end From 6129a63764a0ac3c81d7876db5d9a55d1c5c963c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 13:58:43 +0200 Subject: [PATCH 095/289] Add task to install the migration needed --- README.md | 1 - lib/active_storage/railtie.rb | 2 +- lib/tasks/activestorage.rake | 12 ++++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 lib/tasks/activestorage.rake diff --git a/README.md b/README.md index b768520756..1f0f30098d 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,6 @@ Add `require "active_storage"` to config/application.rb and create a `config/ini - Proper logging - Convert MirrorService to use threading - Read metadata via Marcel? -- Copy over migration to app via rake task - Add Migrator to copy/move between services - Explore direct uploads to cloud - Extract VerifiedKeyWithExpiration into Rails as a feature of MessageVerifier diff --git a/lib/active_storage/railtie.rb b/lib/active_storage/railtie.rb index bf38d5aff5..15ab8aa096 100644 --- a/lib/active_storage/railtie.rb +++ b/lib/active_storage/railtie.rb @@ -1,7 +1,7 @@ require "rails/railtie" module ActiveStorage - class Railtie < Rails::Railtie # :nodoc: + class Engine < Rails::Engine # :nodoc: config.active_storage = ActiveSupport::OrderedOptions.new config.eager_load_namespaces << ActiveStorage diff --git a/lib/tasks/activestorage.rake b/lib/tasks/activestorage.rake new file mode 100644 index 0000000000..ff44958151 --- /dev/null +++ b/lib/tasks/activestorage.rake @@ -0,0 +1,12 @@ +require "fileutils" + +namespace :activestorage do + desc "Copy over the migration needed to the application" + task :migration do + FileUtils.cp \ + File.expand_path("../../active_storage/migration.rb", __FILE__), + Rails.root.join("db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_active_storage_create_tables.rb") + + puts "Now run rails db:migrate to create the tables for Active Storage" + end +end From 87ad273659ef261f51dafee4ca1cc097b9ffd1bd Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:02:09 +0200 Subject: [PATCH 096/289] Extract configuration into config/storage_configuration.yml --- README.md | 11 +++++----- lib/active_storage/config/sites.yml | 25 --------------------- lib/active_storage/railtie.rb | 29 +++++++++++++++++++++++++ lib/active_storage/storage_services.yml | 27 +++++++++++++++++++++++ lib/tasks/activestorage.rake | 15 +++++++++---- 5 files changed, 72 insertions(+), 35 deletions(-) delete mode 100644 lib/active_storage/config/sites.yml create mode 100644 lib/active_storage/storage_services.yml diff --git a/README.md b/README.md index 1f0f30098d..b771629cd0 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,12 @@ class MessagesController < ApplicationController end ``` -## Configuration +## Installation -Add `require "active_storage"` to config/application.rb and create a `config/initializers/active_storage_services.rb` with the following: - -```ruby - -``` +1. Add `require "active_storage"` to config/application.rb. +2. Run rails activestorage:install to create needed directories, migrations, and configuration. +3. Configure the storage service in config/environments/* with `config.active_storage.service = :local` + that references the services configured in config/storage_services.yml. ## Todos diff --git a/lib/active_storage/config/sites.yml b/lib/active_storage/config/sites.yml deleted file mode 100644 index 317ef2b9b7..0000000000 --- a/lib/active_storage/config/sites.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Configuration should be something like this: -# -# config/environments/development.rb -# config.active_storage.service = :local -# -# config/environments/production.rb -# config.active_storage.service = :amazon -local: - service: Disk - root: <%%= File.join(Dir.tmpdir, "active_storage") %> - -amazon: - service: S3 - access_key_id: <%%= Rails.application.secrets.aws[:access_key_id] %> - secret_access_key: <%%= Rails.application.secrets.aws[:secret_access_key] %> - region: us-east-1 - bucket: <%= Rails.application.class.name.remove(/::Application$/).underscore %> - -google: - service: GCS - -mirror: - service: Mirror - primary: amazon - secondaries: google diff --git a/lib/active_storage/railtie.rb b/lib/active_storage/railtie.rb index 15ab8aa096..76894c2e16 100644 --- a/lib/active_storage/railtie.rb +++ b/lib/active_storage/railtie.rb @@ -23,5 +23,34 @@ class Engine < Rails::Engine # :nodoc: extend ActiveStorage::Attached::Macros end end + + config.after_initialize do |app| + config_choice = app.config.active_storage.service + config_file = Pathname.new(Rails.root.join("config/storage_services.yml")) + + if config_choice + raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? + + begin + require "yaml" + require "erb" + configs = YAML.load(ERB.new(config_file.read).result) || {} + + if service_configuration = configs[config_choice.to_s].symbolize_keys + service_name = service_configuration.delete(:service) + + ActiveStorage::Blob.service = ActiveStorage::Service.configure(service_name, service_configuration) + else + raise "Couldn't configure Active Storage as #{config_choice} was not found in #{config_file}" + end + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{config_file}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + rescue => e + raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace + end + end + end end end diff --git a/lib/active_storage/storage_services.yml b/lib/active_storage/storage_services.yml new file mode 100644 index 0000000000..d3f001a27b --- /dev/null +++ b/lib/active_storage/storage_services.yml @@ -0,0 +1,27 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use rails secrets:edit to set the AWS secrets (as shared:aws:access_key_id|secret_access_key) +amazon: + service: S3 + access_key_id: <%= Rails.application.secrets.aws[:access_key_id] %> + secret_access_key: <%= Rails.application.secrets.aws[:secret_access_key] %> + region: us-east-1 + bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +google: + service: GCS + project: your_project + keyfile: <%= Rails.root.join("path/to/gcs.keyfile") %> + bucket: your_own_bucket + +mirror: + service: Mirror + primary: local + secondaries: [ amazon, google ] diff --git a/lib/tasks/activestorage.rake b/lib/tasks/activestorage.rake index ff44958151..09aefef0d8 100644 --- a/lib/tasks/activestorage.rake +++ b/lib/tasks/activestorage.rake @@ -2,10 +2,17 @@ require "fileutils" namespace :activestorage do desc "Copy over the migration needed to the application" - task :migration do - FileUtils.cp \ - File.expand_path("../../active_storage/migration.rb", __FILE__), - Rails.root.join("db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_active_storage_create_tables.rb") + task :install do + FileUtils.mkdir_p Rails.root.join("storage") + FileUtils.mkdir_p Rails.root.join("tmp/storage") + puts "Made storage and tmp/storage directories for development and testing" + + FileUtils.cp File.expand_path("../../active_storage/storage_services.yml", __FILE__), Rails.root.join("config") + puts "Copied default configuration to config/storage_services.yml" + + migration_file_path = "db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_active_storage_create_tables.rb" + FileUtils.cp File.expand_path("../../active_storage/migration.rb", __FILE__), Rails.root.join(migration_file_path) + puts "Copied migration to #{migration_file_path}" puts "Now run rails db:migrate to create the tables for Active Storage" end From ef91f61fe53d6985d4aea891305e64d6340db5cb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:12:53 +0200 Subject: [PATCH 097/289] We are using the try operator --- activestorage.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activestorage.gemspec b/activestorage.gemspec index 4670bd1502..afaad2af6d 100644 --- a/activestorage.gemspec +++ b/activestorage.gemspec @@ -7,7 +7,7 @@ s.homepage = "https://github.com/rails/activestorage" s.license = "MIT" - s.required_ruby_version = ">= 1.9.3" + s.required_ruby_version = ">= 2.3.0" s.add_dependency "activesupport", ">= 5.1" s.add_dependency "activerecord", ">= 5.1" From 740960bc9f8d5b519995c110cc45559f9fb1203b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:13:57 +0200 Subject: [PATCH 098/289] Clearer focus on cloud --- activestorage.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activestorage.gemspec b/activestorage.gemspec index afaad2af6d..ce366d60c2 100644 --- a/activestorage.gemspec +++ b/activestorage.gemspec @@ -3,7 +3,7 @@ s.version = "0.1" s.authors = "David Heinemeier Hansson" s.email = "david@basecamp.com" - s.summary = "Store files in Rails applications" + s.summary = "Attach cloud and local files in Rails applications" s.homepage = "https://github.com/rails/activestorage" s.license = "MIT" From f3aba78ce8436fd09c5bade09092339a21880f7f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:22:40 +0200 Subject: [PATCH 099/289] Convert magic number to constant --- lib/active_storage/service/disk_service.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 6977b5b82e..dc7491310b 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -2,6 +2,8 @@ require "pathname" class ActiveStorage::Service::DiskService < ActiveStorage::Service + CHUNK_SIZE = 65536 + attr_reader :root def initialize(root:) @@ -10,7 +12,7 @@ def initialize(root:) def upload(key, io) File.open(make_path_for(key), "wb") do |file| - while chunk = io.read(65536) + while chunk = io.read(CHUNK_SIZE) file.write(chunk) end end @@ -19,7 +21,7 @@ def upload(key, io) def download(key) if block_given? File.open(path_for(key)) do |file| - while data = file.read(65536) + while data = file.read(CHUNK_SIZE) yield data end end From 89e8b8654621dae37266e4fa6b38627122f66a56 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:31:37 +0200 Subject: [PATCH 100/289] We have the technology! --- lib/active_storage/service/s3_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 811321a172..c94f5ddc63 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -52,7 +52,7 @@ def object_for(key) def stream(key, options = {}, &block) object = object_for(key) - chunk_size = 5242880 # 5 megabytes + chunk_size = 5.megabytes offset = 0 while offset < object.content_length From 0ed18d9671ea7189e737be3d9f7b9f915c31c390 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:35:48 +0200 Subject: [PATCH 101/289] This is even more explaining and upload/download don't have some inherent need to synchronize chunk sizes anyway --- lib/active_storage/service/disk_service.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index dc7491310b..7981226a1e 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -2,8 +2,6 @@ require "pathname" class ActiveStorage::Service::DiskService < ActiveStorage::Service - CHUNK_SIZE = 65536 - attr_reader :root def initialize(root:) @@ -12,7 +10,7 @@ def initialize(root:) def upload(key, io) File.open(make_path_for(key), "wb") do |file| - while chunk = io.read(CHUNK_SIZE) + while chunk = io.read(64.kilobytes) file.write(chunk) end end @@ -21,7 +19,7 @@ def upload(key, io) def download(key) if block_given? File.open(path_for(key)) do |file| - while data = file.read(CHUNK_SIZE) + while data = file.read(64.kilobytes) yield data end end From 6de714a0ea755caafe5758e232582573ac9966a4 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:35:58 +0200 Subject: [PATCH 102/289] Remember to add streaming --- lib/active_storage/service/gcs_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index 18ec1de133..c2f520d996 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -13,6 +13,7 @@ def upload(key, io) bucket.create_file(io, key) end + # FIXME: Add streaming when given a block def download(key) io = file_for(key).download io.rewind From 152c4b07248d4aed4b734721bd634e546a89ef19 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:38:01 +0200 Subject: [PATCH 103/289] Compute checksum and byte_size client side Then we can add integrity checks on uploads to prevent errors in transport. --- lib/active_storage/blob.rb | 18 +++++++++++++++--- lib/active_storage/service/disk_service.rb | 10 ---------- lib/active_storage/service/gcs_service.rb | 14 -------------- lib/active_storage/service/s3_service.rb | 10 ---------- test/service/shared_service_tests.rb | 8 -------- 5 files changed, 15 insertions(+), 45 deletions(-) diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb index 4ce344e2a1..b10dc2c771 100644 --- a/lib/active_storage/blob.rb +++ b/lib/active_storage/blob.rb @@ -42,10 +42,10 @@ def url(expires_in: 5.minutes, disposition: :inline) def upload(io) - service.upload(key, io) + self.checksum = compute_checksum_in_chunks(io) + self.byte_size = io.size - self.checksum = service.checksum(key) - self.byte_size = service.byte_size(key) + service.upload(key, io) end def download @@ -65,4 +65,16 @@ def purge def purge_later ActiveStorage::PurgeJob.perform_later(self) end + + + private + def compute_checksum_in_chunks(io) + Digest::MD5.new.tap do |checksum| + while chunk = io.read(5.megabytes) + checksum << chunk + end + + io.rewind + end.base64digest + end end diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 7981226a1e..98e0f5eb7f 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -36,7 +36,6 @@ def exist?(key) File.exist? path_for(key) end - def url(key, expires_in:, disposition:, filename:) verified_key_with_expiration = ActiveStorage::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) @@ -47,15 +46,6 @@ def url(key, expires_in:, disposition:, filename:) end end - def byte_size(key) - File.size path_for(key) - end - - def checksum(key) - Digest::MD5.file(path_for(key)).hexdigest - end - - private def path_for(key) File.join root, folder_for(key), key diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index c2f520d996..c725afb35c 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -28,27 +28,13 @@ def exist?(key) file_for(key).present? end - def url(key, expires_in:, disposition:, filename:) file_for(key).signed_url(expires: expires_in) + "&" + { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"" }.to_query end - def byte_size(key) - file_for(key).size - end - - def checksum(key) - convert_to_hex base64: file_for(key).md5 - end - - private def file_for(key) bucket.file(key) end - - def convert_to_hex(base64:) - base64.unpack("m0").first.unpack("H*").first - end end diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index c94f5ddc63..cb08893c0e 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -28,21 +28,11 @@ def exist?(key) object_for(key).exists? end - def url(key, expires_in:, disposition:, filename:) object_for(key).presigned_url :get, expires_in: expires_in, response_content_disposition: "#{disposition}; filename=\"#{filename}\"" end - def byte_size(key) - object_for(key).size - end - - def checksum(key) - object_for(key).etag.remove(/"/) - end - - private def object_for(key) bucket.object(key) diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb index 16672ab49b..3676272e27 100644 --- a/test/service/shared_service_tests.rb +++ b/test/service/shared_service_tests.rb @@ -51,13 +51,5 @@ module ActiveStorage::Service::SharedServiceTests @service.delete FIXTURE_KEY assert_not @service.exist?(FIXTURE_KEY) end - - test "sizing" do - assert_equal FIXTURE_FILE.size, @service.byte_size(FIXTURE_KEY) - end - - test "checksumming" do - assert_equal Digest::MD5.hexdigest(FIXTURE_FILE.read), @service.checksum(FIXTURE_KEY) - end end end From 343a4b73084d563d21c86a4b03348e562ea6fe9d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:39:10 +0200 Subject: [PATCH 104/289] There are two --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b771629cd0..e4feab3984 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Active Storage makes it simple to upload and reference files in cloud services, and attach those files to Active Records. It also provides a disk service for testing or local deployments, but the focus is on cloud storage. -## Example +## Examples One attachment: From 8d17bb4bb01600711d144a643d608c2fba95b9b3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:41:22 +0200 Subject: [PATCH 105/289] Need the byte helpers --- lib/active_storage/service/disk_service.rb | 1 + lib/active_storage/service/s3_service.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 98e0f5eb7f..668a837663 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -1,5 +1,6 @@ require "fileutils" require "pathname" +require "active_support/core_ext/numeric/bytes" class ActiveStorage::Service::DiskService < ActiveStorage::Service attr_reader :root diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index cb08893c0e..746a636912 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -1,4 +1,5 @@ require "aws-sdk" +require "active_support/core_ext/numeric/bytes" class ActiveStorage::Service::S3Service < ActiveStorage::Service attr_reader :client, :bucket From ecd07cd905ed3f746c71f496b177e94f8ac2e79d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:41:42 +0200 Subject: [PATCH 106/289] It's base64 now since the clouds expect that Gotta please them clouds. SPEAK THE CLOUD. --- test/blob_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/blob_test.rb b/test/blob_test.rb index 880c656ecc..b06a1af145 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -9,7 +9,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase assert_equal data, blob.download assert_equal data.length, blob.byte_size - assert_equal Digest::MD5.hexdigest(data), blob.checksum + assert_equal Digest::MD5.base64digest(data), blob.checksum end test "urls expiring in 5 minutes" do From 4191d1e675a902e75e224ac8ba5efe7b4db2c308 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:42:09 +0200 Subject: [PATCH 107/289] Dropped from the interface --- lib/active_storage/service.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index 038b6ccb53..0d0ebf6010 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -26,16 +26,7 @@ def exist?(key) raise NotImplementedError end - def url(key, expires_in:, disposition:, filename:) raise NotImplementedError end - - def bytesize(key) - raise NotImplementedError - end - - def checksum(key) - raise NotImplementedError - end end From ef07687262716984db51c173a0378d1eb0664289 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 15:43:14 +0200 Subject: [PATCH 108/289] Escape commands and paths --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e4feab3984..a3ae62f377 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ end ## Installation 1. Add `require "active_storage"` to config/application.rb. -2. Run rails activestorage:install to create needed directories, migrations, and configuration. -3. Configure the storage service in config/environments/* with `config.active_storage.service = :local` - that references the services configured in config/storage_services.yml. +2. Run `rails activestorage:install` to create needed directories, migrations, and configuration. +3. Configure the storage service in `config/environments/*` with `config.active_storage.service = :local` + that references the services configured in `config/storage_services.yml`. ## Todos From 894e1e3183b78d62d873454312882df53a29f850 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 16:01:11 +0200 Subject: [PATCH 109/289] Check integrity after uploads --- lib/active_storage/blob.rb | 2 +- lib/active_storage/service.rb | 4 +++- lib/active_storage/service/disk_service.rb | 10 +++++++++- lib/active_storage/service/gcs_service.rb | 3 ++- lib/active_storage/service/mirror_service.rb | 4 ++-- lib/active_storage/service/s3_service.rb | 3 ++- test/service/shared_service_tests.rb | 17 +++++++++++++++-- 7 files changed, 34 insertions(+), 9 deletions(-) diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb index b10dc2c771..26c116712b 100644 --- a/lib/active_storage/blob.rb +++ b/lib/active_storage/blob.rb @@ -45,7 +45,7 @@ def upload(io) self.checksum = compute_checksum_in_chunks(io) self.byte_size = io.size - service.upload(key, io) + service.upload(key, io, checksum: checksum) end def download diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index 0d0ebf6010..9aab654d80 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -1,5 +1,7 @@ # Abstract class serving as an interface for concrete services. class ActiveStorage::Service + class ActiveStorage::IntegrityError < StandardError; end + def self.configure(service, **options) begin require "active_storage/service/#{service.to_s.downcase}_service" @@ -10,7 +12,7 @@ def self.configure(service, **options) end - def upload(key, io) + def upload(key, io, checksum: nil) raise NotImplementedError end diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 668a837663..6164caf86c 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -9,12 +9,14 @@ def initialize(root:) @root = root end - def upload(key, io) + def upload(key, io, checksum: nil) File.open(make_path_for(key), "wb") do |file| while chunk = io.read(64.kilobytes) file.write(chunk) end end + + ensure_integrity_of(key, checksum) if checksum end def download(key) @@ -59,4 +61,10 @@ def folder_for(key) def make_path_for(key) path_for(key).tap { |path| FileUtils.mkdir_p File.dirname(path) } end + + def ensure_integrity_of(key, checksum) + unless Digest::MD5.file(path_for(key)).base64digest == checksum + raise ActiveStorage::IntegrityError + end + end end diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index c725afb35c..a558791d89 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -9,7 +9,8 @@ def initialize(project:, keyfile:, bucket:) @bucket = @client.bucket(bucket) end - def upload(key, io) + def upload(key, io, checksum: nil) + # FIXME: Ensure integrity by sending the checksum for service side verification bucket.create_file(io, key) end diff --git a/lib/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb index 0d37ad96a3..59ad3fc472 100644 --- a/lib/active_storage/service/mirror_service.rb +++ b/lib/active_storage/service/mirror_service.rb @@ -9,9 +9,9 @@ def initialize(services:) @services = services end - def upload(key, io) + def upload(key, io, checksum: nil) services.collect do |service| - service.upload key, io + service.upload key, io, checksum: checksum io.rewind end end diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 746a636912..fd8ef6e9a6 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -9,7 +9,8 @@ def initialize(access_key_id:, secret_access_key:, region:, bucket:) @bucket = @client.bucket(bucket) end - def upload(key, io) + def upload(key, io, checksum: nil) + # FIXME: Ensure integrity by sending the checksum for service side verification object_for(key).put(body: io) end diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb index 3676272e27..b4c888e77c 100644 --- a/test/service/shared_service_tests.rb +++ b/test/service/shared_service_tests.rb @@ -26,11 +26,11 @@ module ActiveStorage::Service::SharedServiceTests FIXTURE_FILE.rewind end - test "uploading" do + test "uploading with integrity" do begin key = SecureRandom.base58(24) data = "Something else entirely!" - @service.upload(key, StringIO.new(data)) + @service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data)) assert_equal data, @service.download(key) ensure @@ -38,6 +38,19 @@ module ActiveStorage::Service::SharedServiceTests end end + test "upload without integrity" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + + assert_raises(ActiveStorage::IntegrityError) do + @service.upload(key, StringIO.new(data), checksum: "BAD_CHECKSUM") + end + ensure + @service.delete key + end + end + test "downloading" do assert_equal FIXTURE_FILE.read, @service.download(FIXTURE_KEY) end From cd4c2a4d8ef96a63bf38f5aba1727eca5d8ac7e5 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 16:01:18 +0200 Subject: [PATCH 110/289] Helper methods are private --- test/service/mirror_service_test.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/service/mirror_service_test.rb b/test/service/mirror_service_test.rb index 95fe369a4c..45535c754e 100644 --- a/test/service/mirror_service_test.rb +++ b/test/service/mirror_service_test.rb @@ -41,9 +41,10 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase end end - def upload(data, to:) - SecureRandom.base58(24).tap do |key| - @service.upload key, StringIO.new(data) + private + def upload(data, to:) + SecureRandom.base58(24).tap do |key| + @service.upload key, StringIO.new(data), checksum: Digest::MD5.base64digest(data) + end end - end end From 37f7cf8daf0faaf90e6b5548244e6038097097b3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 16:04:08 +0200 Subject: [PATCH 111/289] Documentation, yo! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a3ae62f377..bb14d9df0f 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ end ## Todos +- Document all the classes - Strip Download of its resposibilities and delete class - Proper logging - Convert MirrorService to use threading From 7bd2b3aaa2ec1133025473c78437482ff94c6ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Paca=C5=82a?= Date: Thu, 6 Jul 2017 15:23:00 +0100 Subject: [PATCH 112/289] Use correct syntax in erb block --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb14d9df0f..ab01df471e 100644 --- a/README.md +++ b/README.md @@ -35,15 +35,19 @@ Many attachments: class Message < ApplicationRecord has_many_attached :images end +``` +```erb <%= form_with model: @message do |form| %> <%= form.text_field :title, placeholder: "Title" %>
<%= form.text_area :content %>

- + <%= form.file_field :images, multiple: true %>
<%= form.submit %> <% end %> +``` +```ruby class MessagesController < ApplicationController def create message = Message.create! params.require(:message).permit(:title, :content) From 8fb2e96724f8b4dc2da15312f5769578cb0f3372 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 17:25:40 +0200 Subject: [PATCH 113/289] Describe some of the design differences in AS --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index ab01df471e..ecfc2ed170 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ Active Storage makes it simple to upload and reference files in cloud services, and attach those files to Active Records. It also provides a disk service for testing or local deployments, but the focus is on cloud storage. +## Compared to other storage solutions + +A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in `Blob` and `Attachment` models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses GlobalID to provide polymorphic associations via the join model of `Attachment`, which then connects to the actual `Blob`. + +These `Blob` models are intended to be immutable in spirit. One file, one blob. You can associate the same blob with multiple application models as well. And if you want to do transformations of a given `Blob`, the idea is that you'll simply create a new one, rather than attempt to mutate the existing (though of course you can delete that later if you don't need it). + ## Examples One attachment: From fbeec41e56e3767baf0810f7a0bed46e050a8044 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 6 Jul 2017 17:26:53 +0200 Subject: [PATCH 114/289] Link up main models --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ecfc2ed170..7dde8b6926 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ focus is on cloud storage. ## Compared to other storage solutions -A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in `Blob` and `Attachment` models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses GlobalID to provide polymorphic associations via the join model of `Attachment`, which then connects to the actual `Blob`. +A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in [Blob](https://github.com/rails/activestorage/blob/master/lib/active_storage/blob.rb) and [Attachment](https://github.com/rails/activestorage/blob/master/lib/active_storage/attachment.rb) models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses GlobalID to provide polymorphic associations via the join model of `Attachment`, which then connects to the actual `Blob`. These `Blob` models are intended to be immutable in spirit. One file, one blob. You can associate the same blob with multiple application models as well. And if you want to do transformations of a given `Blob`, the idea is that you'll simply create a new one, rather than attempt to mutate the existing (though of course you can delete that later if you don't need it). From ef3cdc82dae29c2d0bcc3a0832e0476015266ed6 Mon Sep 17 00:00:00 2001 From: Ra'Shaun Stovall Date: Thu, 6 Jul 2017 12:13:40 -0400 Subject: [PATCH 115/289] Fix hash usage consistency. Unless this was intentional, being consistent with: https://github.com/rails/activestorage/blob/master/lib/active_storage/service/s3_service.rb#L8 Just showin' a lil' <3 while perusing the repo @dhh --- lib/active_storage/service/s3_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index fd8ef6e9a6..b4d0ed4bee 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -48,7 +48,7 @@ def stream(key, options = {}, &block) offset = 0 while offset < object.content_length - yield object.read(options.merge(:range => "bytes=#{offset}-#{offset + chunk_size - 1}")) + yield object.read(options.merge(range: "bytes=#{offset}-#{offset + chunk_size - 1}")) offset += chunk_size end end From d065a6861664ee92f1fd7a9ffbef5427df2463c1 Mon Sep 17 00:00:00 2001 From: Stanislav Gospodinov Date: Thu, 6 Jul 2017 19:14:10 +0100 Subject: [PATCH 116/289] Adding server side integrity check for GCS Service --- lib/active_storage/service/gcs_service.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index a558791d89..532da08df3 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -10,8 +10,11 @@ def initialize(project:, keyfile:, bucket:) end def upload(key, io, checksum: nil) - # FIXME: Ensure integrity by sending the checksum for service side verification - bucket.create_file(io, key) + begin + bucket.create_file(io, key, md5: checksum) + rescue Google::Cloud::InvalidArgumentError + raise ActiveStorage::IntegrityError + end end # FIXME: Add streaming when given a block From 1a5219ce87ea6b5d534dd70c0d121c8fa8d2c1f0 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 6 Jul 2017 14:25:14 -0400 Subject: [PATCH 117/289] Style --- lib/active_storage/service/gcs_service.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index 532da08df3..9ac33d68f3 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -10,11 +10,9 @@ def initialize(project:, keyfile:, bucket:) end def upload(key, io, checksum: nil) - begin - bucket.create_file(io, key, md5: checksum) - rescue Google::Cloud::InvalidArgumentError - raise ActiveStorage::IntegrityError - end + bucket.create_file(io, key, md5: checksum) + rescue Google::Cloud::InvalidArgumentError + raise ActiveStorage::IntegrityError end # FIXME: Add streaming when given a block From f8539164c046e162531728b15f764fa8248704f1 Mon Sep 17 00:00:00 2001 From: John Williams Date: Thu, 6 Jul 2017 14:00:57 -0500 Subject: [PATCH 118/289] Send checksum to S3 to verify file integrity --- lib/active_storage/service/s3_service.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index b4d0ed4bee..413789e2b5 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -10,8 +10,7 @@ def initialize(access_key_id:, secret_access_key:, region:, bucket:) end def upload(key, io, checksum: nil) - # FIXME: Ensure integrity by sending the checksum for service side verification - object_for(key).put(body: io) + object_for(key).put(body: io, content_md5: checksum) end def download(key) From a2e864fa13c7d3d56a95a2f46d525597f989f938 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 6 Jul 2017 15:31:31 -0400 Subject: [PATCH 119/289] Fix test * S3 fails fast if the Content-MD5 header on an upload request is an invalid checksum. Send a valid but incorrect checksum. * Rescue the service-specific exception and raise the generic one. --- lib/active_storage/service/s3_service.rb | 2 ++ test/service/shared_service_tests.rb | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 413789e2b5..963a41af17 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -11,6 +11,8 @@ def initialize(access_key_id:, secret_access_key:, region:, bucket:) def upload(key, io, checksum: nil) object_for(key).put(body: io, content_md5: checksum) + rescue Aws::S3::Errors::BadDigest + raise ActiveStorage::IntegrityError end def download(key) diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb index b4c888e77c..dfa0d61656 100644 --- a/test/service/shared_service_tests.rb +++ b/test/service/shared_service_tests.rb @@ -3,7 +3,7 @@ require "yaml" SERVICE_CONFIGURATIONS = begin - YAML.load_file(File.expand_path("../configurations.yml", __FILE__)).deep_symbolize_keys + YAML.load_file(File.expand_path("../configurations.yml", __FILE__)).deep_symbolize_keys rescue Errno::ENOENT puts "Missing service configuration file in test/services/configurations.yml" end @@ -44,7 +44,7 @@ module ActiveStorage::Service::SharedServiceTests data = "Something else entirely!" assert_raises(ActiveStorage::IntegrityError) do - @service.upload(key, StringIO.new(data), checksum: "BAD_CHECKSUM") + @service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest("bad data")) end ensure @service.delete key From 3e5206f8d138492f7f2d94a279f5c42bda7d5ddb Mon Sep 17 00:00:00 2001 From: John Williams Date: Thu, 6 Jul 2017 14:53:23 -0500 Subject: [PATCH 120/289] Correct config path in error message --- test/service/shared_service_tests.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb index dfa0d61656..377670f4a0 100644 --- a/test/service/shared_service_tests.rb +++ b/test/service/shared_service_tests.rb @@ -5,7 +5,7 @@ SERVICE_CONFIGURATIONS = begin YAML.load_file(File.expand_path("../configurations.yml", __FILE__)).deep_symbolize_keys rescue Errno::ENOENT - puts "Missing service configuration file in test/services/configurations.yml" + puts "Missing service configuration file in test/service/configurations.yml" end module ActiveStorage::Service::SharedServiceTests From 04e6728dd50cbe80a8f0039e9718dc96bc46e701 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 6 Jul 2017 17:00:21 -0400 Subject: [PATCH 121/289] Remove unnecessary method delegations --- lib/active_storage/service/mirror_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb index 59ad3fc472..1ec0930e6c 100644 --- a/lib/active_storage/service/mirror_service.rb +++ b/lib/active_storage/service/mirror_service.rb @@ -3,7 +3,7 @@ class ActiveStorage::Service::MirrorService < ActiveStorage::Service attr_reader :services - delegate :download, :exist?, :url, :byte_size, :checksum, to: :primary_service + delegate :download, :exist?, :url, to: :primary_service def initialize(services:) @services = services From fb88ff78b312edacc83db783ffa48cec15fd0764 Mon Sep 17 00:00:00 2001 From: Robin Dupret Date: Thu, 6 Jul 2017 21:53:51 +0200 Subject: [PATCH 122/289] Fix the migration class name Due to Active Support auto loading feature, the migration class shouldn't be name-spaced under the `ActiveStorage` constant, otherwise, running the migrations would throw an error. --- lib/active_storage/migration.rb | 2 +- test/database/create_users_migration.rb | 2 +- test/database/setup.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/active_storage/migration.rb b/lib/active_storage/migration.rb index c0400abe3b..fe08e80127 100644 --- a/lib/active_storage/migration.rb +++ b/lib/active_storage/migration.rb @@ -1,4 +1,4 @@ -class ActiveStorage::CreateTables < ActiveRecord::Migration[5.1] +class ActiveStorageCreateTables < ActiveRecord::Migration[5.1] def change create_table :active_storage_blobs do |t| t.string :key diff --git a/test/database/create_users_migration.rb b/test/database/create_users_migration.rb index 15be1938a9..a0b72a90ee 100644 --- a/test/database/create_users_migration.rb +++ b/test/database/create_users_migration.rb @@ -1,4 +1,4 @@ -class ActiveStorage::CreateUsers < ActiveRecord::Migration[5.1] +class ActiveStorageCreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| t.string :name diff --git a/test/database/setup.rb b/test/database/setup.rb index 828edd86dd..5921412b0c 100644 --- a/test/database/setup.rb +++ b/test/database/setup.rb @@ -2,5 +2,5 @@ require_relative "create_users_migration" ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') -ActiveStorage::CreateTables.migrate(:up) -ActiveStorage::CreateUsers.migrate(:up) +ActiveStorageCreateTables.migrate(:up) +ActiveStorageCreateUsers.migrate(:up) From ddcd6df27ff8fc984789923227dd1d27e92e45b7 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 6 Jul 2017 17:27:08 -0400 Subject: [PATCH 123/289] Use safe navigation --- lib/active_storage/service/gcs_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index 9ac33d68f3..e09fa484ff 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -23,7 +23,7 @@ def download(key) end def delete(key) - file_for(key).try(:delete) + file_for(key)&.delete end def exist?(key) From b7b933abccb7c014b00834b71ac43bccd056eb36 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 6 Jul 2017 17:27:27 -0400 Subject: [PATCH 124/289] Test deleting a nonexistent key --- test/service/shared_service_tests.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb index 377670f4a0..b7d619843b 100644 --- a/test/service/shared_service_tests.rb +++ b/test/service/shared_service_tests.rb @@ -64,5 +64,11 @@ module ActiveStorage::Service::SharedServiceTests @service.delete FIXTURE_KEY assert_not @service.exist?(FIXTURE_KEY) end + + test "deleting nonexistent key" do + assert_nothing_raised do + @service.delete SecureRandom.base58(24) + end + end end end From d3527bbdafbc2769320f30568011dd926a5eb1f5 Mon Sep 17 00:00:00 2001 From: Bradly Feeley Date: Thu, 6 Jul 2017 17:18:46 -0700 Subject: [PATCH 125/289] Fixing typo in Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7dde8b6926..06af5a5417 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ end ## Todos - Document all the classes -- Strip Download of its resposibilities and delete class +- Strip Download of its responsibilities and delete class - Proper logging - Convert MirrorService to use threading - Read metadata via Marcel? From 07a62e63bd4effb819fd641155578b07eb5557ed Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 6 Jul 2017 23:45:51 -0400 Subject: [PATCH 126/289] Create db/migrate if it doesn't exist --- lib/tasks/activestorage.rake | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tasks/activestorage.rake b/lib/tasks/activestorage.rake index 09aefef0d8..17dab0854a 100644 --- a/lib/tasks/activestorage.rake +++ b/lib/tasks/activestorage.rake @@ -11,6 +11,7 @@ namespace :activestorage do puts "Copied default configuration to config/storage_services.yml" migration_file_path = "db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_active_storage_create_tables.rb" + FileUtils.mkdir_p Rails.root.join("db/migrate") FileUtils.cp File.expand_path("../../active_storage/migration.rb", __FILE__), Rails.root.join(migration_file_path) puts "Copied migration to #{migration_file_path}" From c9846fc0e8d4faf4f4686c5bfe548431cdae837c Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Fri, 7 Jul 2017 06:17:56 -0400 Subject: [PATCH 127/289] Bundle google-cloud-storage instead of the full Google SDK --- Gemfile | 2 +- Gemfile.lock | 91 +--------------------------------------------------- 2 files changed, 2 insertions(+), 91 deletions(-) diff --git a/Gemfile b/Gemfile index 60b2596c53..1797d20194 100644 --- a/Gemfile +++ b/Gemfile @@ -8,4 +8,4 @@ gem 'byebug' gem 'sqlite3' gem 'aws-sdk', require: false -gem 'google-cloud', require: false +gem 'google-cloud-storage', require: false diff --git a/Gemfile.lock b/Gemfile.lock index afef3518aa..c4f3d9fd89 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,94 +66,15 @@ GEM mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - google-cloud (0.34.0) - google-cloud-bigquery (~> 0.27.0) - google-cloud-datastore (~> 1.0) - google-cloud-dns (~> 0.25.0) - google-cloud-error_reporting (~> 0.25.0) - google-cloud-language (~> 0.26.0) - google-cloud-logging (~> 1.0) - google-cloud-monitoring (~> 0.24.0) - google-cloud-pubsub (~> 0.25.0) - google-cloud-resource_manager (~> 0.26.0) - google-cloud-spanner (~> 0.21.0) - google-cloud-speech (~> 0.24.0) - google-cloud-storage (~> 1.2) - google-cloud-trace (~> 0.25.0) - google-cloud-translate (~> 1.0) - google-cloud-video_intelligence (~> 0.20.0) - google-cloud-vision (~> 0.24.0) - google-cloud-bigquery (0.27.0) - google-api-client (~> 0.13.0) - google-cloud-core (~> 1.0) google-cloud-core (1.0.0) google-cloud-env (~> 1.0) googleauth (~> 0.5.1) - google-cloud-datastore (1.0.1) - google-cloud-core (~> 1.0) - google-gax (~> 0.8.0) - google-protobuf (~> 3.2.0) - google-cloud-dns (0.25.0) - google-api-client (~> 0.13.0) - google-cloud-core (~> 1.0) - zonefile (~> 1.04) google-cloud-env (1.0.0) faraday (~> 0.11) - google-cloud-error_reporting (0.25.0) - google-cloud-core (~> 1.0) - google-gax (~> 0.8.0) - stackdriver-core (~> 1.1) - google-cloud-language (0.26.2) - google-cloud-core (~> 1.0) - google-gax (~> 0.8.2) - google-cloud-logging (1.1.0) - google-cloud-core (~> 1.0) - google-gax (~> 0.8.0) - stackdriver-core (~> 1.1) - google-cloud-monitoring (0.24.0) - google-gax (~> 0.8.0) - google-cloud-pubsub (0.25.0) - google-cloud-core (~> 1.0) - google-gax (~> 0.8.0) - grpc-google-iam-v1 (~> 0.6.8) - google-cloud-resource_manager (0.26.0) - google-api-client (~> 0.13.0) - google-cloud-core (~> 1.0) - google-cloud-spanner (0.21.0) - concurrent-ruby (~> 1.0) - google-cloud-core (~> 1.0) - google-gax (~> 0.8.1) - grpc (~> 1.1) - grpc-google-iam-v1 (~> 0.6.8) - google-cloud-speech (0.24.0) - google-cloud-core (~> 1.0) - google-gax (~> 0.8.2) google-cloud-storage (1.2.0) digest-crc (~> 0.4) google-api-client (~> 0.13.0) google-cloud-core (~> 1.0) - google-cloud-trace (0.25.0) - google-cloud-core (~> 1.0) - google-gax (~> 0.8.0) - stackdriver-core (~> 1.1) - google-cloud-translate (1.0.0) - google-cloud-core (~> 1.0) - googleauth (~> 0.5.1) - google-cloud-video_intelligence (0.20.0) - google-gax (~> 0.8.0) - google-cloud-vision (0.24.0) - google-cloud-core (~> 1.0) - google-gax (~> 0.8.0) - google-gax (0.8.4) - google-protobuf (~> 3.2) - googleapis-common-protos (~> 1.3.5) - googleauth (~> 0.5.1) - grpc (~> 1.0) - rly (~> 0.2.3) - google-protobuf (3.2.0.2) - googleapis-common-protos (1.3.5) - google-protobuf (~> 3.2) - grpc (~> 1.0) googleauth (0.5.1) faraday (~> 0.9) jwt (~> 1.4) @@ -162,13 +83,6 @@ GEM multi_json (~> 1.11) os (~> 0.9) signet (~> 0.7) - grpc (1.4.1) - google-protobuf (~> 3.1) - googleauth (~> 0.5.1) - grpc-google-iam-v1 (0.6.8) - googleapis-common-protos (~> 1.3.1) - googleauth (~> 0.5.1) - grpc (~> 1.0) httpclient (2.8.3) i18n (0.8.4) jmespath (1.3.1) @@ -205,19 +119,16 @@ GEM declarative-option (< 0.2.0) uber (< 0.2.0) retriable (3.0.2) - rly (0.2.3) signet (0.7.3) addressable (~> 2.3) faraday (~> 0.9) jwt (~> 1.5) multi_json (~> 1.10) sqlite3 (1.3.13) - stackdriver-core (1.1.0) thread_safe (0.3.6) tzinfo (1.2.3) thread_safe (~> 0.1) uber (0.1.0) - zonefile (1.06) PLATFORMS ruby @@ -227,7 +138,7 @@ DEPENDENCIES aws-sdk bundler (~> 1.15) byebug - google-cloud + google-cloud-storage rake sqlite3 From 0e54b8be0546a4b575f4fe585f4da053cecf3c3d Mon Sep 17 00:00:00 2001 From: Marat Galiev Date: Fri, 7 Jul 2017 14:45:50 +0300 Subject: [PATCH 128/289] Update README.md For the first look It's obvious, but makes sense I think, and raises undefined method `active_storage' on migration run for example. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06af5a5417..08bd0844c2 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ end ## Installation -1. Add `require "active_storage"` to config/application.rb. +1. Add `require "active_storage"` to config/application.rb, after `require "rails/all"` line. 2. Run `rails activestorage:install` to create needed directories, migrations, and configuration. 3. Configure the storage service in `config/environments/*` with `config.active_storage.service = :local` that references the services configured in `config/storage_services.yml`. From 44aab4d65931175628b1aa6b1fe3e4152b64e6a9 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 7 Jul 2017 17:13:02 +0200 Subject: [PATCH 129/289] It is an engine (because of tasks) not a railtie --- lib/active_storage.rb | 2 +- lib/active_storage/{railtie.rb => engine.rb} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename lib/active_storage/{railtie.rb => engine.rb} (98%) diff --git a/lib/active_storage.rb b/lib/active_storage.rb index f72fe0d017..8b867f0145 100644 --- a/lib/active_storage.rb +++ b/lib/active_storage.rb @@ -1,5 +1,5 @@ require "active_record" -require "active_storage/railtie" if defined?(Rails) +require "active_storage/engine" if defined?(Rails) module ActiveStorage extend ActiveSupport::Autoload diff --git a/lib/active_storage/railtie.rb b/lib/active_storage/engine.rb similarity index 98% rename from lib/active_storage/railtie.rb rename to lib/active_storage/engine.rb index 76894c2e16..3512be0468 100644 --- a/lib/active_storage/railtie.rb +++ b/lib/active_storage/engine.rb @@ -1,4 +1,4 @@ -require "rails/railtie" +require "rails/engine" module ActiveStorage class Engine < Rails::Engine # :nodoc: From 27f87b68b26e4ac68fa9bf05e6003c61cdca854d Mon Sep 17 00:00:00 2001 From: Robin Dupret Date: Fri, 7 Jul 2017 14:20:38 +0200 Subject: [PATCH 130/289] Add some documentation --- lib/active_storage/attached/macros.rb | 26 ++++++++++++++++++++-- lib/active_storage/attached/many.rb | 17 +++++++++++++++ lib/active_storage/attached/one.rb | 17 +++++++++++++++ lib/active_storage/disk_controller.rb | 12 ++++++++++- lib/active_storage/migration.rb | 2 +- lib/active_storage/service.rb | 31 ++++++++++++++++++++++++++- 6 files changed, 100 insertions(+), 5 deletions(-) diff --git a/lib/active_storage/attached/macros.rb b/lib/active_storage/attached/macros.rb index 96493d1215..1e0f9a6b7e 100644 --- a/lib/active_storage/attached/macros.rb +++ b/lib/active_storage/attached/macros.rb @@ -1,7 +1,18 @@ module ActiveStorage::Attached::Macros + # Specifies the relation between a single attachment and the model. + # + # class User < ActiveRecord::Base + # has_one_attached :avatar + # end + # + # There is no column defined on the model side, Active Storage takes + # care of the mapping between your records and the attachment. + # + # If the +:dependent+ option isn't set, the attachment will be purged + # (i.e. destroyed) whenever the record is destroyed. def has_one_attached(name, dependent: :purge_later) define_method(name) do - instance_variable_get("@active_storage_attached_#{name}") || + instance_variable_get("@active_storage_attached_#{name}") || instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::One.new(name, self)) end @@ -10,9 +21,20 @@ def has_one_attached(name, dependent: :purge_later) end end + # Specifies the relation between multiple attachments and the model. + # + # class Gallery < ActiveRecord::Base + # has_many_attached :photos + # end + # + # There are no columns defined on the model side, Active Storage takes + # care of the mapping between your records and the attachments. + # + # If the +:dependent+ option isn't set, all the attachments will be purged + # (i.e. destroyed) whenever the record is destroyed. def has_many_attached(name, dependent: :purge_later) define_method(name) do - instance_variable_get("@active_storage_attached_#{name}") || + instance_variable_get("@active_storage_attached_#{name}") || instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::Many.new(name, self)) end diff --git a/lib/active_storage/attached/many.rb b/lib/active_storage/attached/many.rb index f1535dfbc6..99d980196a 100644 --- a/lib/active_storage/attached/many.rb +++ b/lib/active_storage/attached/many.rb @@ -1,20 +1,36 @@ +# Representation of multiple attachments to a model. class ActiveStorage::Attached::Many < ActiveStorage::Attached delegate_missing_to :attachments + # Returns all the associated attachment records. + # + # You don't have to call this method to access the attachments' methods as + # they are all available at the model level. def attachments @attachments ||= ActiveStorage::Attachment.where(record_gid: record.to_gid.to_s, name: name) end + # Associates one or several attachments with the current record, saving + # them to the database. def attach(*attachables) @attachments = attachments | Array(attachables).flatten.collect do |attachable| ActiveStorage::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) end end + # Checks the presence of attachments. + # + # class Gallery < ActiveRecord::Base + # has_many_attached :photos + # end + # + # Gallery.new.photos.attached? # => false def attached? attachments.any? end + # Directly purges each associated attachment (i.e. destroys the blobs and + # attachments and deletes the files on the service). def purge if attached? attachments.each(&:purge) @@ -22,6 +38,7 @@ def purge end end + # Purges each associated attachment through the queuing system. def purge_later if attached? attachments.each(&:purge_later) diff --git a/lib/active_storage/attached/one.rb b/lib/active_storage/attached/one.rb index d08d265992..80e4cb6234 100644 --- a/lib/active_storage/attached/one.rb +++ b/lib/active_storage/attached/one.rb @@ -1,18 +1,34 @@ +# Representation of a single attachment to a model. class ActiveStorage::Attached::One < ActiveStorage::Attached delegate_missing_to :attachment + # Returns the associated attachment record. + # + # You don't have to call this method to access the attachment's methods as + # they are all available at the model level. def attachment @attachment ||= ActiveStorage::Attachment.find_by(record_gid: record.to_gid.to_s, name: name) end + # Associates a given attachment with the current record, saving it to the + # database. def attach(attachable) @attachment = ActiveStorage::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) end + # Checks the presence of the attachment. + # + # class User < ActiveRecord::Base + # has_one_attached :avatar + # end + # + # User.new.avatar.attached? # => false def attached? attachment.present? end + # Directly purges the attachment (i.e. destroys the blob and + # attachment and deletes the file on the service). def purge if attached? attachment.purge @@ -20,6 +36,7 @@ def purge end end + # Purges the attachment through the queuing system. def purge_later if attached? attachment.purge_later diff --git a/lib/active_storage/disk_controller.rb b/lib/active_storage/disk_controller.rb index 3eba86c213..9d5b52d66f 100644 --- a/lib/active_storage/disk_controller.rb +++ b/lib/active_storage/disk_controller.rb @@ -4,11 +4,21 @@ require "active_support/core_ext/object/inclusion" +# This controller is a wrapper around local file downloading. It allows you to +# make abstraction of the URL generation logic and to serve files with expiry +# if you are using the +Disk+ service. +# +# By default, mounting the Active Storage engine inside your application will +# define a +/rails/blobs/:encoded_key+ route that will reference this controller's +# +show+ action and will be used to serve local files. +# +# A URL for an attachment can be generated through its +#url+ method, that +# will use the aforementioned route. class ActiveStorage::DiskController < ActionController::Base def show if key = decode_verified_key blob = ActiveStorage::Blob.find_by!(key: key) - + if stale?(etag: blob.checksum) send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param end diff --git a/lib/active_storage/migration.rb b/lib/active_storage/migration.rb index fe08e80127..dce666edc1 100644 --- a/lib/active_storage/migration.rb +++ b/lib/active_storage/migration.rb @@ -1,4 +1,4 @@ -class ActiveStorageCreateTables < ActiveRecord::Migration[5.1] +class ActiveStorageCreateTables < ActiveRecord::Migration[5.1] # :nodoc: def change create_table :active_storage_blobs do |t| t.string :key diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index 9aab654d80..f15958fda9 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -1,4 +1,34 @@ # Abstract class serving as an interface for concrete services. +# +# The available services are: +# +# * +Disk+, to manage attachments saved directly on the hard drive. +# * +GCS+, to manage attachments through Google Cloud Storage. +# * +S3+, to manage attachments through Amazon S3. +# * +Mirror+, to be able to use several services to manage attachments. +# +# Inside a Rails application, you can set-up your services through the +# generated config/storage_services.yml file and reference one +# of the aforementioned constant under the +service+ key. For example: +# +# local: +# service: Disk +# root: <%= Rails.root.join("storage") %> +# +# You can checkout the service's constructor to know which keys are required. +# +# Then, in your application's configuration, you can specify the service to +# use like this: +# +# config.active_storage.service = :local +# +# If you are using Active Storage outside of a Ruby on Rails application, you +# can configure the service to use like this: +# +# ActiveStorage::Blob.service = ActiveStorage::Service.configure( +# :Disk, +# root: Pathname("/foo/bar/storage") +# ) class ActiveStorage::Service class ActiveStorage::IntegrityError < StandardError; end @@ -11,7 +41,6 @@ def self.configure(service, **options) end end - def upload(key, io, checksum: nil) raise NotImplementedError end From 29d65e90802bd93e8d36d4c67569bed0b013cb99 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Fri, 7 Jul 2017 19:16:41 -0400 Subject: [PATCH 131/289] Change type of created_at columns from time to datetime We intend to store a date and time, not merely a time. --- lib/active_storage/migration.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/active_storage/migration.rb b/lib/active_storage/migration.rb index fe08e80127..433dd5026f 100644 --- a/lib/active_storage/migration.rb +++ b/lib/active_storage/migration.rb @@ -1,13 +1,13 @@ class ActiveStorageCreateTables < ActiveRecord::Migration[5.1] def change create_table :active_storage_blobs do |t| - t.string :key - t.string :filename - t.string :content_type - t.text :metadata - t.integer :byte_size - t.string :checksum - t.time :created_at + t.string :key + t.string :filename + t.string :content_type + t.text :metadata + t.integer :byte_size + t.string :checksum + t.datetime :created_at t.index [ :key ], unique: true end @@ -17,7 +17,7 @@ def change t.string :record_gid t.integer :blob_id - t.time :created_at + t.datetime :created_at t.index :record_gid t.index :blob_id From 800e957abdd68fcfc7ee424711dc3f32e81c77b8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 8 Jul 2017 18:51:55 +0200 Subject: [PATCH 132/289] Add a brief roadmap section --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 08bd0844c2..74a9581e6b 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,13 @@ end - Convert MirrorService to use threading - Read metadata via Marcel? - Add Migrator to copy/move between services -- Explore direct uploads to cloud +- [Explore direct uploads to cloud](https://github.com/rails/activestorage/pull/19) - Extract VerifiedKeyWithExpiration into Rails as a feature of MessageVerifier +## Roadmap + +This separate repository is a staging ground for eventual inclusion in rails/rails prior to the Rails 5.2 release. It is not intended to be a long-term stand-alone repository. Compatibility with prior versions of Rails is not a development priority either. + ## License Active Storage is released under the [MIT License](https://opensource.org/licenses/MIT). From 255b1a149c8be93ede25404c53933bde3acc2dc2 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 8 Jul 2017 15:08:59 -0700 Subject: [PATCH 133/289] Tests: skip GCS/AWS service tests if unconfigured --- test/service/shared_service_tests.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb index b7d619843b..99bc252eea 100644 --- a/test/service/shared_service_tests.rb +++ b/test/service/shared_service_tests.rb @@ -6,6 +6,7 @@ YAML.load_file(File.expand_path("../configurations.yml", __FILE__)).deep_symbolize_keys rescue Errno::ENOENT puts "Missing service configuration file in test/service/configurations.yml" + {} end module ActiveStorage::Service::SharedServiceTests From 92a4f5b3f42c92ec272eb28adebea83faa012afb Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 8 Jul 2017 15:11:27 -0700 Subject: [PATCH 134/289] Disk service: use binary IO throughout, not UTF-8 --- lib/active_storage/service/disk_service.rb | 12 ++++-------- test/service/shared_service_tests.rb | 10 ++++------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 6164caf86c..d1e82525d8 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -1,5 +1,6 @@ require "fileutils" require "pathname" +require "digest/md5" require "active_support/core_ext/numeric/bytes" class ActiveStorage::Service::DiskService < ActiveStorage::Service @@ -10,24 +11,19 @@ def initialize(root:) end def upload(key, io, checksum: nil) - File.open(make_path_for(key), "wb") do |file| - while chunk = io.read(64.kilobytes) - file.write(chunk) - end - end - + IO.copy_stream(io, make_path_for(key)) ensure_integrity_of(key, checksum) if checksum end def download(key) if block_given? File.open(path_for(key)) do |file| - while data = file.read(64.kilobytes) + while data = file.binread(64.kilobytes) yield data end end else - File.open path_for(key), &:read + File.binread path_for(key) end end diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb index 99bc252eea..e799c24c35 100644 --- a/test/service/shared_service_tests.rb +++ b/test/service/shared_service_tests.rb @@ -13,18 +13,16 @@ module ActiveStorage::Service::SharedServiceTests extend ActiveSupport::Concern FIXTURE_KEY = SecureRandom.base58(24) - FIXTURE_FILE = StringIO.new("Hello world!") + FIXTURE_DATA = "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000\020\000\000\000\020\001\003\000\000\000%=m\"\000\000\000\006PLTE\000\000\000\377\377\377\245\331\237\335\000\000\0003IDATx\234c\370\377\237\341\377_\206\377\237\031\016\2603\334?\314p\1772\303\315\315\f7\215\031\356\024\203\320\275\317\f\367\201R\314\f\017\300\350\377\177\000Q\206\027(\316]\233P\000\000\000\000IEND\256B`\202".force_encoding(Encoding::BINARY) included do setup do @service = self.class.const_get(:SERVICE) - @service.upload FIXTURE_KEY, FIXTURE_FILE - FIXTURE_FILE.rewind + @service.upload FIXTURE_KEY, StringIO.new(FIXTURE_DATA) end teardown do @service.delete FIXTURE_KEY - FIXTURE_FILE.rewind end test "uploading with integrity" do @@ -53,7 +51,7 @@ module ActiveStorage::Service::SharedServiceTests end test "downloading" do - assert_equal FIXTURE_FILE.read, @service.download(FIXTURE_KEY) + assert_equal FIXTURE_DATA, @service.download(FIXTURE_KEY) end test "existing" do @@ -65,7 +63,7 @@ module ActiveStorage::Service::SharedServiceTests @service.delete FIXTURE_KEY assert_not @service.exist?(FIXTURE_KEY) end - + test "deleting nonexistent key" do assert_nothing_raised do @service.delete SecureRandom.base58(24) From 03120ecb50016fc210945d0824c11d9308b28372 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 8 Jul 2017 15:13:02 -0700 Subject: [PATCH 135/289] Disk storage: ensure URLs end with the blob filename since some user agents don't respect Content-Disposition filename --- lib/active_storage/disk_controller.rb | 4 ++-- lib/active_storage/engine.rb | 2 +- lib/active_storage/service/disk_service.rb | 4 ++-- test/blob_test.rb | 2 +- test/disk_controller_test.rb | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/active_storage/disk_controller.rb b/lib/active_storage/disk_controller.rb index 9d5b52d66f..7149cc17a6 100644 --- a/lib/active_storage/disk_controller.rb +++ b/lib/active_storage/disk_controller.rb @@ -9,8 +9,8 @@ # if you are using the +Disk+ service. # # By default, mounting the Active Storage engine inside your application will -# define a +/rails/blobs/:encoded_key+ route that will reference this controller's -# +show+ action and will be used to serve local files. +# define a +/rails/blobs/:encoded_key/*filename+ route that will reference this +# controller's +show+ action and will be used to serve local files. # # A URL for an attachment can be generated through its +#url+ method, that # will use the aforementioned route. diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index 3512be0468..d35d3c16db 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -11,7 +11,7 @@ class Engine < Rails::Engine # :nodoc: config.after_initialize do |app| app.routes.prepend do - get "/rails/blobs/:encoded_key" => "active_storage/disk#show", as: :rails_disk_blob + get "/rails/blobs/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob end end end diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 6164caf86c..5576b3b125 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -43,9 +43,9 @@ def url(key, expires_in:, disposition:, filename:) verified_key_with_expiration = ActiveStorage::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) if defined?(Rails) && defined?(Rails.application) - Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition) + Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition, filename: filename) else - "/rails/blobs/#{verified_key_with_expiration}?disposition=#{disposition}" + "/rails/blobs/#{verified_key_with_expiration}/#{filename}?disposition=#{disposition}" end end diff --git a/test/blob_test.rb b/test/blob_test.rb index b06a1af145..ac9ca37487 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -23,6 +23,6 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase private def expected_url_for(blob, disposition: :inline) - "/rails/blobs/#{ActiveStorage::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}?disposition=#{disposition}" + "/rails/blobs/#{ActiveStorage::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}/#{blob.filename}?disposition=#{disposition}" end end diff --git a/test/disk_controller_test.rb b/test/disk_controller_test.rb index 3d7f4ba6bd..119ee5828f 100644 --- a/test/disk_controller_test.rb +++ b/test/disk_controller_test.rb @@ -10,7 +10,7 @@ class ActiveStorage::DiskControllerTest < ActionController::TestCase Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| routes.draw do - get "/rails/blobs/:encoded_key" => "active_storage/disk#show", as: :rails_disk_blob + get "/rails/blobs/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob end end @@ -21,13 +21,13 @@ class ActiveStorage::DiskControllerTest < ActionController::TestCase end test "showing blob inline" do - get :show, params: { encoded_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes) } + get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes) } assert_equal "inline; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end test "sending blob as attachment" do - get :show, params: { encoded_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), disposition: :attachment } + get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), disposition: :attachment } assert_equal "attachment; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end From 6116313da4996ef99dcb45e2b9ac90ef073caabc Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 8 Jul 2017 15:41:14 -0700 Subject: [PATCH 136/289] Mirror: explicit primary service and list of mirrors Pass separate primary service and list of mirrors rather than treating the first of the services list as the primary. Nice fit for keyword args, and something we've long wanted in the equivalent Basecamp file repository. Upload returns the results of the underlying service uploads rather than the io.rewind result. Rewind before uploading rather than afterward, and demonstrate that behavior with a test. Test that more than one mirror works. --- lib/active_storage/service/mirror_service.rb | 19 +++++++++---------- test/service/mirror_service_test.rb | 18 +++++++++++++----- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb index 1ec0930e6c..7ec166aace 100644 --- a/lib/active_storage/service/mirror_service.rb +++ b/lib/active_storage/service/mirror_service.rb @@ -1,18 +1,17 @@ require "active_support/core_ext/module/delegation" class ActiveStorage::Service::MirrorService < ActiveStorage::Service - attr_reader :services + attr_reader :primary, :mirrors - delegate :download, :exist?, :url, to: :primary_service + delegate :download, :exist?, :url, to: :primary - def initialize(services:) - @services = services + def initialize(primary:, mirrors:) + @primary, @mirrors = primary, mirrors end def upload(key, io, checksum: nil) - services.collect do |service| - service.upload key, io, checksum: checksum - io.rewind + each_service.collect do |service| + service.upload key, io.tap(&:rewind), checksum: checksum end end @@ -21,13 +20,13 @@ def delete(key) end private - def primary_service - services.first + def each_service(&block) + [ primary, *mirrors ].each(&block) end def perform_across_services(method, *args) # FIXME: Convert to be threaded - services.collect do |service| + each_service.collect do |service| service.public_send method, *args end end diff --git a/test/service/mirror_service_test.rb b/test/service/mirror_service_test.rb index 45535c754e..10af41c0a8 100644 --- a/test/service/mirror_service_test.rb +++ b/test/service/mirror_service_test.rb @@ -3,9 +3,11 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase PRIMARY_DISK_SERVICE = ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) - SECONDARY_DISK_SERVICE = ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage_mirror")) + MIRROR_SERVICES = (1..3).map do |i| + ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage_mirror_#{i}")) + end - SERVICE = ActiveStorage::Service.configure :Mirror, services: [ PRIMARY_DISK_SERVICE, SECONDARY_DISK_SERVICE ] + SERVICE = ActiveStorage::Service.configure :Mirror, primary: PRIMARY_DISK_SERVICE, mirrors: MIRROR_SERVICES include ActiveStorage::Service::SharedServiceTests @@ -15,7 +17,9 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase key = upload(data, to: @service) assert_equal data, PRIMARY_DISK_SERVICE.download(key) - assert_equal data, SECONDARY_DISK_SERVICE.download(key) + MIRROR_SERVICES.each do |mirror| + assert_equal data, mirror.download(key) + end ensure @service.delete key end @@ -31,7 +35,9 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase test "deleting from all services" do @service.delete FIXTURE_KEY assert_not PRIMARY_DISK_SERVICE.exist?(FIXTURE_KEY) - assert_not SECONDARY_DISK_SERVICE.exist?(FIXTURE_KEY) + MIRROR_SERVICES.each do |mirror| + assert_not mirror.exist?(FIXTURE_KEY) + end end test "URL generation in primary service" do @@ -44,7 +50,9 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase private def upload(data, to:) SecureRandom.base58(24).tap do |key| - @service.upload key, StringIO.new(data), checksum: Digest::MD5.base64digest(data) + io = StringIO.new(data).tap(&:read) + @service.upload key, io, checksum: Digest::MD5.base64digest(data) + assert io.eof? end end end From e5503399c0bb992ec1fd47b4bc371b8aef679e37 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 8 Jul 2017 16:55:50 -0700 Subject: [PATCH 137/289] Configure services that reference other services * Move service configuration from the Engine to Service * Delegate configuration mechanics to internal Service::Configurator * Delegate service building to the concrete Service classes, allowing them to configure composed services. * Implement for the Mirror service. --- lib/active_storage/engine.rb | 39 +++++++++----------- lib/active_storage/service.rb | 16 ++++---- lib/active_storage/service/configurator.rb | 31 ++++++++++++++++ lib/active_storage/service/mirror_service.rb | 11 ++++++ lib/active_storage/storage_services.yml | 2 +- test/service/disk_service_test.rb | 2 +- test/service/gcs_service_test.rb | 2 +- test/service/mirror_service_test.rb | 27 ++++++++------ test/service/s3_service_test.rb | 2 +- test/test_helper.rb | 2 +- 10 files changed, 90 insertions(+), 44 deletions(-) create mode 100644 lib/active_storage/service/configurator.rb diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index d35d3c16db..d066a689eb 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -25,31 +25,28 @@ class Engine < Rails::Engine # :nodoc: end config.after_initialize do |app| - config_choice = app.config.active_storage.service - config_file = Pathname.new(Rails.root.join("config/storage_services.yml")) - - if config_choice + if config_choice = app.config.active_storage.service + config_file = Pathname.new(Rails.root.join("config/storage_services.yml")) raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? - begin - require "yaml" - require "erb" - configs = YAML.load(ERB.new(config_file.read).result) || {} + require "yaml" + require "erb" - if service_configuration = configs[config_choice.to_s].symbolize_keys - service_name = service_configuration.delete(:service) - - ActiveStorage::Blob.service = ActiveStorage::Service.configure(service_name, service_configuration) - else - raise "Couldn't configure Active Storage as #{config_choice} was not found in #{config_file}" + configs = + begin + YAML.load(ERB.new(config_file.read).result) || {} + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{config_file}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + end + + ActiveStorage::Blob.service = + begin + ActiveStorage::Service.configure config_choice, configs + rescue => e + raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace end - rescue Psych::SyntaxError => e - raise "YAML syntax error occurred while parsing #{config_file}. " \ - "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ - "Error: #{e.message}" - rescue => e - raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace - end end end end diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index f15958fda9..1a6a55739f 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -32,13 +32,15 @@ class ActiveStorage::Service class ActiveStorage::IntegrityError < StandardError; end - def self.configure(service, **options) - begin - require "active_storage/service/#{service.to_s.downcase}_service" - ActiveStorage::Service.const_get(:"#{service}Service").new(**options) - rescue LoadError => e - puts "Couldn't configure service: #{service} (#{e.message})" - end + def self.configure(service_name, configurations) + require 'active_storage/service/configurator' + Configurator.new(service_name, configurations).build + end + + # Override in subclasses that stitch together multiple services and hence + # need to do additional lookups from configurations. See MirrorService. + def self.build(config, configurations) #:nodoc: + new(config) end def upload(key, io, checksum: nil) diff --git a/lib/active_storage/service/configurator.rb b/lib/active_storage/service/configurator.rb new file mode 100644 index 0000000000..5054e07ec7 --- /dev/null +++ b/lib/active_storage/service/configurator.rb @@ -0,0 +1,31 @@ +class ActiveStorage::Service::Configurator #:nodoc: + def initialize(service_name, configurations) + @service_name, @configurations = service_name.to_sym, configurations.symbolize_keys + end + + def build + service_class.build(service_config.except(:service), @configurations) + end + + private + def service_class + resolve service_class_name + end + + def service_class_name + service_config.fetch :service do + raise "Missing Active Storage `service: …` configuration for #{service_config.inspect}" + end + end + + def service_config + @configurations.fetch @service_name do + raise "Missing configuration for the #{@service_name.inspect} Active Storage service. Configurations available for #{@configurations.keys.inspect}" + end + end + + def resolve(service_class_name) + require "active_storage/service/#{service_class_name.to_s.downcase}_service" + ActiveStorage::Service.const_get(:"#{service_class_name}Service") + end +end diff --git a/lib/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb index 7ec166aace..eec1f2af65 100644 --- a/lib/active_storage/service/mirror_service.rb +++ b/lib/active_storage/service/mirror_service.rb @@ -5,6 +5,17 @@ class ActiveStorage::Service::MirrorService < ActiveStorage::Service delegate :download, :exist?, :url, to: :primary + # Stitch together from named configuration. + def self.build(mirror_config, all_configurations) #:nodoc: + primary = ActiveStorage::Service.configure(mirror_config.fetch(:primary), all_configurations) + + mirrors = mirror_config.fetch(:mirrors).collect do |service_name| + ActiveStorage::Service.configure(service_name.to_sym, all_configurations) + end + + new primary: primary, mirrors: mirrors + end + def initialize(primary:, mirrors:) @primary, @mirrors = primary, mirrors end diff --git a/lib/active_storage/storage_services.yml b/lib/active_storage/storage_services.yml index d3f001a27b..a93304d88f 100644 --- a/lib/active_storage/storage_services.yml +++ b/lib/active_storage/storage_services.yml @@ -24,4 +24,4 @@ google: mirror: service: Mirror primary: local - secondaries: [ amazon, google ] + mirrors: [ amazon, google ] diff --git a/test/service/disk_service_test.rb b/test/service/disk_service_test.rb index 5dd7cff303..bd2e68277e 100644 --- a/test/service/disk_service_test.rb +++ b/test/service/disk_service_test.rb @@ -2,7 +2,7 @@ require "service/shared_service_tests" class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase - SERVICE = ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) + SERVICE = ActiveStorage::Service.configure(:test, test: { service: "Disk", root: File.join(Dir.tmpdir, "active_storage") }) include ActiveStorage::Service::SharedServiceTests end diff --git a/test/service/gcs_service_test.rb b/test/service/gcs_service_test.rb index 42f9cd3061..7d4700498b 100644 --- a/test/service/gcs_service_test.rb +++ b/test/service/gcs_service_test.rb @@ -2,7 +2,7 @@ if SERVICE_CONFIGURATIONS[:gcs] class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase - SERVICE = ActiveStorage::Service.configure(:GCS, SERVICE_CONFIGURATIONS[:gcs]) + SERVICE = ActiveStorage::Service.configure(:gcs, SERVICE_CONFIGURATIONS) include ActiveStorage::Service::SharedServiceTests diff --git a/test/service/mirror_service_test.rb b/test/service/mirror_service_test.rb index 10af41c0a8..6fee3cadd2 100644 --- a/test/service/mirror_service_test.rb +++ b/test/service/mirror_service_test.rb @@ -2,12 +2,17 @@ require "service/shared_service_tests" class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase - PRIMARY_DISK_SERVICE = ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) - MIRROR_SERVICES = (1..3).map do |i| - ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage_mirror_#{i}")) - end + mirror_config = (1..3).map do |i| + [ "mirror_#{i}", + service: "Disk", + root: File.join(Dir.tmpdir, "active_storage_mirror_#{i}") ] + end.to_h - SERVICE = ActiveStorage::Service.configure :Mirror, primary: PRIMARY_DISK_SERVICE, mirrors: MIRROR_SERVICES + config = mirror_config.merge \ + mirror: { service: "Mirror", primary: 'primary', mirrors: mirror_config.keys }, + primary: { service: "Disk", root: File.join(Dir.tmpdir, "active_storage") } + + SERVICE = ActiveStorage::Service.configure :mirror, config include ActiveStorage::Service::SharedServiceTests @@ -16,8 +21,8 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase data = "Something else entirely!" key = upload(data, to: @service) - assert_equal data, PRIMARY_DISK_SERVICE.download(key) - MIRROR_SERVICES.each do |mirror| + assert_equal data, SERVICE.primary.download(key) + SERVICE.mirrors.each do |mirror| assert_equal data, mirror.download(key) end ensure @@ -27,22 +32,22 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase test "downloading from primary service" do data = "Something else entirely!" - key = upload(data, to: PRIMARY_DISK_SERVICE) + key = upload(data, to: SERVICE.primary) assert_equal data, @service.download(key) end test "deleting from all services" do @service.delete FIXTURE_KEY - assert_not PRIMARY_DISK_SERVICE.exist?(FIXTURE_KEY) - MIRROR_SERVICES.each do |mirror| + assert_not SERVICE.primary.exist?(FIXTURE_KEY) + SERVICE.mirrors.each do |mirror| assert_not mirror.exist?(FIXTURE_KEY) end end test "URL generation in primary service" do travel_to Time.now do - assert_equal PRIMARY_DISK_SERVICE.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt"), + assert_equal SERVICE.primary.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt"), @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") end end diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index 604dfd6c60..e8cc4cb5f4 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -2,7 +2,7 @@ if SERVICE_CONFIGURATIONS[:s3] class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase - SERVICE = ActiveStorage::Service.configure(:S3, SERVICE_CONFIGURATIONS[:s3]) + SERVICE = ActiveStorage::Service.configure(:s3, SERVICE_CONFIGURATIONS) include ActiveStorage::Service::SharedServiceTests end diff --git a/test/test_helper.rb b/test/test_helper.rb index dcabe33c18..b374a777dd 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,7 +7,7 @@ require "active_storage" require "active_storage/service" -ActiveStorage::Blob.service = ActiveStorage::Service.configure(:Disk, root: File.join(Dir.tmpdir, "active_storage")) +ActiveStorage::Blob.service = ActiveStorage::Service.configure(:test, test: { service: 'Disk', root: File.join(Dir.tmpdir, "active_storage") }) require "active_storage/verified_key_with_expiration" ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") From f7f864c6f422774f42b009c0ab790a51ca1a0f3b Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 8 Jul 2017 17:42:02 -0700 Subject: [PATCH 138/289] =?UTF-8?q?Travis=20CI=20=F0=9F=92=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..324890e2fb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: ruby +sudo: false +bundler: true + +script: bundle exec rake + +rvm: + - 2.3 + - 2.4 + - ruby-head + +matrix: + allow_failures: + - rvm: ruby-head + fast_finish: true From 1a17cfb9d9719c8458fb1259371c173627b96d8f Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sun, 9 Jul 2017 03:19:55 -0700 Subject: [PATCH 139/289] Service: clarify Service.build arguments First arg is config for the service we're instantiating. Second arg is service configurations so we can look up and configure other services by name. --- lib/active_storage/service.rb | 4 ++-- lib/active_storage/service/mirror_service.rb | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index 1a6a55739f..6978ce6429 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -39,8 +39,8 @@ def self.configure(service_name, configurations) # Override in subclasses that stitch together multiple services and hence # need to do additional lookups from configurations. See MirrorService. - def self.build(config, configurations) #:nodoc: - new(config) + def self.build(service_config, all_configurations) #:nodoc: + new(service_config) end def upload(key, io, checksum: nil) diff --git a/lib/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb index eec1f2af65..8a51a75684 100644 --- a/lib/active_storage/service/mirror_service.rb +++ b/lib/active_storage/service/mirror_service.rb @@ -6,10 +6,10 @@ class ActiveStorage::Service::MirrorService < ActiveStorage::Service delegate :download, :exist?, :url, to: :primary # Stitch together from named configuration. - def self.build(mirror_config, all_configurations) #:nodoc: - primary = ActiveStorage::Service.configure(mirror_config.fetch(:primary), all_configurations) + def self.build(service_config, all_configurations) #:nodoc: + primary = ActiveStorage::Service.configure(service_config.fetch(:primary), all_configurations) - mirrors = mirror_config.fetch(:mirrors).collect do |service_name| + mirrors = service_config.fetch(:mirrors).collect do |service_name| ActiveStorage::Service.configure(service_name.to_sym, all_configurations) end From 4d292fc0e75e35e177806b1ea821455fc0bc021c Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sun, 9 Jul 2017 04:23:21 -0700 Subject: [PATCH 140/289] Clarify how a service can build other composed services * Service.build takes the literal YAML config hash for the service and a reference to the Configurator that's doing the building. * Services that compose additional services can use the Configurator to look them up and build them by name. See MirrorService for an example. References #23 --- lib/active_storage/service.rb | 19 +++++++--- lib/active_storage/service/configurator.rb | 37 +++++++++----------- lib/active_storage/service/mirror_service.rb | 14 +++----- test/service/disk_service_test.rb | 2 +- test/test_helper.rb | 5 ++- 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index 6978ce6429..021c695a07 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -32,15 +32,24 @@ class ActiveStorage::Service class ActiveStorage::IntegrityError < StandardError; end + extend ActiveSupport::Autoload + autoload :Configurator + + # Configure an Active Storage service by name from a set of configurations, + # typically loaded from a YAML file. The Active Storage engine uses this + # to set the global Active Storage service when the app boots. def self.configure(service_name, configurations) - require 'active_storage/service/configurator' - Configurator.new(service_name, configurations).build + Configurator.build(service_name, configurations) end # Override in subclasses that stitch together multiple services and hence - # need to do additional lookups from configurations. See MirrorService. - def self.build(service_config, all_configurations) #:nodoc: - new(service_config) + # need to build additional services using the configurator. + # + # Passes the configurator and all of the service's config as keyword args. + # + # See MirrorService for an example. + def self.build(configurator:, service: nil, **service_config) #:nodoc: + new(**service_config) end def upload(key, io, checksum: nil) diff --git a/lib/active_storage/service/configurator.rb b/lib/active_storage/service/configurator.rb index 5054e07ec7..2159c80df9 100644 --- a/lib/active_storage/service/configurator.rb +++ b/lib/active_storage/service/configurator.rb @@ -1,31 +1,28 @@ class ActiveStorage::Service::Configurator #:nodoc: - def initialize(service_name, configurations) - @service_name, @configurations = service_name.to_sym, configurations.symbolize_keys + attr_reader :configurations + + def self.build(service_name, configurations) + new(configurations).build(service_name) end - def build - service_class.build(service_config.except(:service), @configurations) + def initialize(configurations) + @configurations = configurations.symbolize_keys + end + + def build(service_name) + config = config_for(service_name.to_sym) + resolve(config.fetch(:service)).build(**config, configurator: self) end private - def service_class - resolve service_class_name - end - - def service_class_name - service_config.fetch :service do - raise "Missing Active Storage `service: …` configuration for #{service_config.inspect}" + def config_for(name) + configurations.fetch name do + raise "Missing configuration for the #{name.inspect} Active Storage service. Configurations available for #{configurations.keys.inspect}" end end - def service_config - @configurations.fetch @service_name do - raise "Missing configuration for the #{@service_name.inspect} Active Storage service. Configurations available for #{@configurations.keys.inspect}" - end - end - - def resolve(service_class_name) - require "active_storage/service/#{service_class_name.to_s.downcase}_service" - ActiveStorage::Service.const_get(:"#{service_class_name}Service") + def resolve(class_name) + require "active_storage/service/#{class_name.to_s.downcase}_service" + ActiveStorage::Service.const_get(:"#{class_name}Service") end end diff --git a/lib/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb index 8a51a75684..54465cad05 100644 --- a/lib/active_storage/service/mirror_service.rb +++ b/lib/active_storage/service/mirror_service.rb @@ -5,15 +5,11 @@ class ActiveStorage::Service::MirrorService < ActiveStorage::Service delegate :download, :exist?, :url, to: :primary - # Stitch together from named configuration. - def self.build(service_config, all_configurations) #:nodoc: - primary = ActiveStorage::Service.configure(service_config.fetch(:primary), all_configurations) - - mirrors = service_config.fetch(:mirrors).collect do |service_name| - ActiveStorage::Service.configure(service_name.to_sym, all_configurations) - end - - new primary: primary, mirrors: mirrors + # Stitch together from named services. + def self.build(primary:, mirrors:, configurator:, **options) #:nodoc: + new \ + primary: configurator.build(primary), + mirrors: mirrors.collect { |name| configurator.build name } end def initialize(primary:, mirrors:) diff --git a/test/service/disk_service_test.rb b/test/service/disk_service_test.rb index bd2e68277e..94df146024 100644 --- a/test/service/disk_service_test.rb +++ b/test/service/disk_service_test.rb @@ -2,7 +2,7 @@ require "service/shared_service_tests" class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase - SERVICE = ActiveStorage::Service.configure(:test, test: { service: "Disk", root: File.join(Dir.tmpdir, "active_storage") }) + SERVICE = ActiveStorage::Service::DiskService.new(root: File.join(Dir.tmpdir, "active_storage")) include ActiveStorage::Service::SharedServiceTests end diff --git a/test/test_helper.rb b/test/test_helper.rb index b374a777dd..e24c45f656 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,9 +5,8 @@ require "byebug" require "active_storage" - -require "active_storage/service" -ActiveStorage::Blob.service = ActiveStorage::Service.configure(:test, test: { service: 'Disk', root: File.join(Dir.tmpdir, "active_storage") }) +require "active_storage/service/disk_service" +ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: File.join(Dir.tmpdir, "active_storage")) require "active_storage/verified_key_with_expiration" ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") From f1489c22202858fe003968e93cc4dc435859483e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 14:19:19 +0200 Subject: [PATCH 141/289] Match new configurator needs --- test/service/configurations-example.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/service/configurations-example.yml b/test/service/configurations-example.yml index 031197342a..fc6e9dc81e 100644 --- a/test/service/configurations-example.yml +++ b/test/service/configurations-example.yml @@ -1,11 +1,13 @@ # Copy this file to configurations.yml and edit the credentials to match your IAM test account and bucket s3: + service: S3 access_key_id: secret_access_key: region: bucket: gcs: + service: GCS project: keyfile: bucket: From 01109dc00357b758c5809708f510bcef6442350b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 14:42:46 +0200 Subject: [PATCH 142/289] Use class methods scope now that we have multiple --- lib/active_storage/service.rb | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index 021c695a07..86f867c293 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -35,21 +35,23 @@ class ActiveStorage::IntegrityError < StandardError; end extend ActiveSupport::Autoload autoload :Configurator - # Configure an Active Storage service by name from a set of configurations, - # typically loaded from a YAML file. The Active Storage engine uses this - # to set the global Active Storage service when the app boots. - def self.configure(service_name, configurations) - Configurator.build(service_name, configurations) - end + class << self + # Configure an Active Storage service by name from a set of configurations, + # typically loaded from a YAML file. The Active Storage engine uses this + # to set the global Active Storage service when the app boots. + def configure(service_name, configurations) + Configurator.build(service_name, configurations) + end - # Override in subclasses that stitch together multiple services and hence - # need to build additional services using the configurator. - # - # Passes the configurator and all of the service's config as keyword args. - # - # See MirrorService for an example. - def self.build(configurator:, service: nil, **service_config) #:nodoc: - new(**service_config) + # Override in subclasses that stitch together multiple services and hence + # need to build additional services using the configurator. + # + # Passes the configurator and all of the service's config as keyword args. + # + # See MirrorService for an example. + def build(configurator:, service: nil, **service_config) #:nodoc: + new(**service_config) + end end def upload(key, io, checksum: nil) From 8561d679e846c5c36ae76604d42f6d06933e1150 Mon Sep 17 00:00:00 2001 From: dixpac Date: Sun, 9 Jul 2017 14:53:36 +0200 Subject: [PATCH 143/289] Symbolize all keys inside configuration nested hash Since configuration is a nested hash we need to symbolize all keys of the hash. Othervise fetcing will fail on start --- lib/active_storage/service/configurator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/service/configurator.rb b/lib/active_storage/service/configurator.rb index 2159c80df9..00ae24d251 100644 --- a/lib/active_storage/service/configurator.rb +++ b/lib/active_storage/service/configurator.rb @@ -6,7 +6,7 @@ def self.build(service_name, configurations) end def initialize(configurations) - @configurations = configurations.symbolize_keys + @configurations = configurations.deep_symbolize_keys end def build(service_name) From a1a068061ad2e30424c73b530ad8f96f0454f25f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 15:24:34 +0200 Subject: [PATCH 144/289] Ensure binary encoding for downloading --- lib/active_storage/service/s3_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 963a41af17..09886ca863 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -19,7 +19,7 @@ def download(key) if block_given? stream(key, &block) else - object_for(key).get.body.read + object_for(key).get.body.read.force_encoding(Encoding::BINARY) end end From d361befedf5e864310d083e7804a8cf9b9409f3c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 15:24:46 +0200 Subject: [PATCH 145/289] Example of keyfile specification --- test/service/configurations-example.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/service/configurations-example.yml b/test/service/configurations-example.yml index fc6e9dc81e..8bcc57f05a 100644 --- a/test/service/configurations-example.yml +++ b/test/service/configurations-example.yml @@ -8,6 +8,17 @@ s3: gcs: service: GCS + keyfile: { + type: "service_account", + project_id: "", + private_key_id: "", + private_key: "", + client_email: "", + client_id: "", + auth_uri: "https://accounts.google.com/o/oauth2/auth", + token_uri: "https://accounts.google.com/o/oauth2/token", + auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs", + client_x509_cert_url: "" + } project: - keyfile: bucket: From 4bfe7af68f1e1a7d02ace760ac1a5c5a4462edb2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 17:04:28 +0200 Subject: [PATCH 146/289] Instrument and log the services --- lib/active_storage/engine.rb | 8 ++++ lib/active_storage/log_subscriber.rb | 51 ++++++++++++++++++++++ lib/active_storage/service.rb | 16 +++++++ lib/active_storage/service/disk_service.rb | 49 +++++++++++++++------ lib/active_storage/service/gcs_service.rb | 38 +++++++++++----- lib/active_storage/service/s3_service.rb | 38 ++++++++++++---- test/test_helper.rb | 1 + 7 files changed, 169 insertions(+), 32 deletions(-) create mode 100644 lib/active_storage/log_subscriber.rb diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index d066a689eb..adcf42ee58 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -6,6 +6,14 @@ class Engine < Rails::Engine # :nodoc: config.eager_load_namespaces << ActiveStorage + initializer "active_storage.logger" do + require "active_storage/service" + + config.after_initialize do |app| + ActiveStorage::Service.logger = app.config.active_storage.logger || Rails.logger + end + end + initializer "active_storage.routes" do require "active_storage/disk_controller" diff --git a/lib/active_storage/log_subscriber.rb b/lib/active_storage/log_subscriber.rb new file mode 100644 index 0000000000..b3f130a572 --- /dev/null +++ b/lib/active_storage/log_subscriber.rb @@ -0,0 +1,51 @@ +require "active_support/log_subscriber" + +# Implements the ActiveSupport::LogSubscriber for logging notifications when +# email is delivered or received. +class ActiveStorage::LogSubscriber < ActiveSupport::LogSubscriber + def service_upload(event) + message = color("Uploaded file to key: #{key_in(event)}", GREEN) + message << color(" (checksum: #{event.payload[:checksum]})", GREEN) if event.payload[:checksum] + info event, message + end + + def service_download(event) + info event, color("Downloaded file from key: #{key_in(event)}", BLUE) + end + + def service_delete(event) + info event, color("Deleted file from key: #{key_in(event)}", RED) + end + + def service_exist(event) + debug event, color("Checked if file exist at key: #{key_in(event)} (#{event.payload[:exist] ? "yes" : "no"})", BLUE) + end + + def service_url(event) + debug event, color("Generated URL for file at key: #{key_in(event)} (#{event.payload[:url]})", BLUE) + end + + # Use the logger configured for ActiveStorage::Base.logger + def logger + ActiveStorage::Service.logger + end + + private + def info(event, colored_message) + super log_prefix_for_service(event) + colored_message + end + + def debug(event, colored_message) + super log_prefix_for_service(event) + colored_message + end + + def log_prefix_for_service(event) + color " #{event.payload[:service]} Storage (#{event.duration.round(1)}ms) ", CYAN + end + + def key_in(event) + event.payload[:key] + end +end + +ActiveStorage::LogSubscriber.attach_to :active_storage diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index 86f867c293..f50849b694 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -1,3 +1,5 @@ +require_relative "log_subscriber" + # Abstract class serving as an interface for concrete services. # # The available services are: @@ -35,6 +37,8 @@ class ActiveStorage::IntegrityError < StandardError; end extend ActiveSupport::Autoload autoload :Configurator + class_attribute :logger + class << self # Configure an Active Storage service by name from a set of configurations, # typically loaded from a YAML file. The Active Storage engine uses this @@ -73,4 +77,16 @@ def exist?(key) def url(key, expires_in:, disposition:, filename:) raise NotImplementedError end + + private + def instrument(operation, key, payload = {}, &block) + ActiveSupport::Notifications.instrument( + "service_#{operation}.active_storage", + payload.merge(key: key, service: service_name), &block) + end + + def service_name + # ActiveStorage::Service::DiskService => Disk + self.class.name.split("::").third.remove("Service") + end end diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index f6c4fd8c4b..e2d9191189 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -11,37 +11,60 @@ def initialize(root:) end def upload(key, io, checksum: nil) - IO.copy_stream(io, make_path_for(key)) - ensure_integrity_of(key, checksum) if checksum + instrument :upload, key, checksum: checksum do + IO.copy_stream(io, make_path_for(key)) + ensure_integrity_of(key, checksum) if checksum + end end def download(key) if block_given? - File.open(path_for(key)) do |file| - while data = file.binread(64.kilobytes) - yield data + instrument :streaming_download, key do + File.open(path_for(key)) do |file| + while data = file.binread(64.kilobytes) + yield data + end end end else - File.binread path_for(key) + instrument :download, key do + File.binread path_for(key) + end end end def delete(key) - File.delete path_for(key) rescue Errno::ENOENT # Ignore files already deleted + instrument :delete, key do + begin + File.delete path_for(key) + rescue Errno::ENOENT + # Ignore files already deleted + end + end end def exist?(key) - File.exist? path_for(key) + instrument :exist, key do |payload| + answer = File.exist? path_for(key) + payload[:exist] = answer + answer + end end def url(key, expires_in:, disposition:, filename:) - verified_key_with_expiration = ActiveStorage::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) + instrument :url, key do |payload| + verified_key_with_expiration = ActiveStorage::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) - if defined?(Rails) && defined?(Rails.application) - Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition, filename: filename) - else - "/rails/blobs/#{verified_key_with_expiration}/#{filename}?disposition=#{disposition}" + generated_url = + if defined?(Rails) && defined?(Rails.application) + Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition, filename: filename) + else + "/rails/blobs/#{verified_key_with_expiration}/#{filename}?disposition=#{disposition}" + end + + payload[:url] = generated_url + + generated_url end end diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index e09fa484ff..bca4ab5331 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -10,29 +10,47 @@ def initialize(project:, keyfile:, bucket:) end def upload(key, io, checksum: nil) - bucket.create_file(io, key, md5: checksum) - rescue Google::Cloud::InvalidArgumentError - raise ActiveStorage::IntegrityError + instrument :upload, key, checksum: checksum do + begin + bucket.create_file(io, key, md5: checksum) + rescue Google::Cloud::InvalidArgumentError + raise ActiveStorage::IntegrityError + end + end end # FIXME: Add streaming when given a block def download(key) - io = file_for(key).download - io.rewind - io.read + instrument :download, key do + io = file_for(key).download + io.rewind + io.read + end end def delete(key) - file_for(key)&.delete + instrument :delete, key do + file_for(key)&.delete + end end def exist?(key) - file_for(key).present? + instrument :exist, key do |payload| + answer = file_for(key).present? + payload[:exist] = answer + answer + end end def url(key, expires_in:, disposition:, filename:) - file_for(key).signed_url(expires: expires_in) + "&" + - { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"" }.to_query + instrument :url, key do |payload| + generated_url = file_for(key).signed_url(expires: expires_in) + "&" + + { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"" }.to_query + + payload[:url] = generated_url + + generated_url + end end private diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 09886ca863..53890751ee 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -10,30 +10,50 @@ def initialize(access_key_id:, secret_access_key:, region:, bucket:) end def upload(key, io, checksum: nil) - object_for(key).put(body: io, content_md5: checksum) - rescue Aws::S3::Errors::BadDigest - raise ActiveStorage::IntegrityError + instrument :upload, key, checksum: checksum do + begin + object_for(key).put(body: io, content_md5: checksum) + rescue Aws::S3::Errors::BadDigest + raise ActiveStorage::IntegrityError + end + end end def download(key) if block_given? - stream(key, &block) + instrument :streaming_download, key do + stream(key, &block) + end else - object_for(key).get.body.read.force_encoding(Encoding::BINARY) + instrument :download, key do + object_for(key).get.body.read.force_encoding(Encoding::BINARY) + end end end def delete(key) - object_for(key).delete + instrument :delete, key do + object_for(key).delete + end end def exist?(key) - object_for(key).exists? + instrument :exist, key do |payload| + answer = object_for(key).exists? + payload[:exist] = answer + answer + end end def url(key, expires_in:, disposition:, filename:) - object_for(key).presigned_url :get, expires_in: expires_in, - response_content_disposition: "#{disposition}; filename=\"#{filename}\"" + instrument :url, key do |payload| + generated_url = object_for(key).presigned_url :get, expires_in: expires_in, + response_content_disposition: "#{disposition}; filename=\"#{filename}\"" + + payload[:url] = generated_url + + generated_url + end end private diff --git a/test/test_helper.rb b/test/test_helper.rb index e24c45f656..b67296a659 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,6 +7,7 @@ require "active_storage" require "active_storage/service/disk_service" ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: File.join(Dir.tmpdir, "active_storage")) +ActiveStorage::Service.logger = ActiveSupport::Logger.new(STDOUT) require "active_storage/verified_key_with_expiration" ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") From 5bb3f63b318932de0bc21b164d5eb2530a718c3d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 17:04:37 +0200 Subject: [PATCH 147/289] Test URL generation for S3 and Disk --- test/service/disk_service_test.rb | 5 +++++ test/service/s3_service_test.rb | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/test/service/disk_service_test.rb b/test/service/disk_service_test.rb index 94df146024..c5404f55e6 100644 --- a/test/service/disk_service_test.rb +++ b/test/service/disk_service_test.rb @@ -5,4 +5,9 @@ class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase SERVICE = ActiveStorage::Service::DiskService.new(root: File.join(Dir.tmpdir, "active_storage")) include ActiveStorage::Service::SharedServiceTests + + test "url generation" do + assert_match /rails\/blobs\/.*\/avatar\.png\?disposition=inline/, + @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") + end end diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index e8cc4cb5f4..3e1838e393 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -5,6 +5,11 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase SERVICE = ActiveStorage::Service.configure(:s3, SERVICE_CONFIGURATIONS) include ActiveStorage::Service::SharedServiceTests + + test "signed URL generation" do + assert_match /rails-activestorage\.s3\.amazonaws\.com.*response-content-disposition=inline.*avatar\.png/, + @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") + end end else puts "Skipping S3 Service tests because no S3 configuration was supplied" From 7593b7704c60de37dfdea3fafd5def1db7a9258c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 17:04:59 +0200 Subject: [PATCH 148/289] Proper logging is now in place --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 74a9581e6b..625b960624 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,6 @@ end - Document all the classes - Strip Download of its responsibilities and delete class -- Proper logging - Convert MirrorService to use threading - Read metadata via Marcel? - Add Migrator to copy/move between services From b1cf901d282c869c670fa4246be5ce40116112c9 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 17:21:53 +0200 Subject: [PATCH 149/289] Copypasta comments # Conflicts: # lib/active_storage/engine.rb # lib/active_storage/service.rb # lib/active_storage/service/disk_service.rb # lib/active_storage/service/s3_service.rb # test/service/s3_service_test.rb # test/test_helper.rb --- lib/active_storage/log_subscriber.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/active_storage/log_subscriber.rb b/lib/active_storage/log_subscriber.rb index b3f130a572..5c486b9161 100644 --- a/lib/active_storage/log_subscriber.rb +++ b/lib/active_storage/log_subscriber.rb @@ -1,7 +1,5 @@ require "active_support/log_subscriber" -# Implements the ActiveSupport::LogSubscriber for logging notifications when -# email is delivered or received. class ActiveStorage::LogSubscriber < ActiveSupport::LogSubscriber def service_upload(event) message = color("Uploaded file to key: #{key_in(event)}", GREEN) @@ -25,7 +23,6 @@ def service_url(event) debug event, color("Generated URL for file at key: #{key_in(event)} (#{event.payload[:url]})", BLUE) end - # Use the logger configured for ActiveStorage::Base.logger def logger ActiveStorage::Service.logger end From a19d943f1de7d856d74ff8a0e1806da99be26076 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 18:03:13 +0200 Subject: [PATCH 150/289] Direct uploads for S3 --- Gemfile | 1 + Gemfile.lock | 4 +++ lib/active_storage/blob.rb | 8 +++++ .../direct_uploads_controller.rb | 14 ++++++++ lib/active_storage/engine.rb | 3 +- lib/active_storage/routes.rb | 2 ++ lib/active_storage/service.rb | 4 +++ lib/active_storage/service/disk_service.rb | 2 +- lib/active_storage/service/s3_service.rb | 11 ++++++ test/blob_test.rb | 2 +- test/direct_uploads_controller_test.rb | 36 +++++++++++++++++++ test/disk_controller_test.rb | 9 ----- test/service/disk_service_test.rb | 2 +- test/service/s3_service_test.rb | 31 ++++++++++++++++ test/service/shared_service_tests.rb | 8 ----- test/test_helper.rb | 22 ++++++++++++ 16 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 lib/active_storage/direct_uploads_controller.rb create mode 100644 lib/active_storage/routes.rb create mode 100644 test/direct_uploads_controller_test.rb diff --git a/Gemfile b/Gemfile index 1797d20194..8e2031ed85 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ gem 'rake' gem 'byebug' gem 'sqlite3' +gem 'httparty' gem 'aws-sdk', require: false gem 'google-cloud-storage', require: false diff --git a/Gemfile.lock b/Gemfile.lock index c4f3d9fd89..797996cdc3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -83,6 +83,8 @@ GEM multi_json (~> 1.11) os (~> 0.9) signet (~> 0.7) + httparty (0.15.5) + multi_xml (>= 0.5.2) httpclient (2.8.3) i18n (0.8.4) jmespath (1.3.1) @@ -100,6 +102,7 @@ GEM mini_portile2 (2.1.0) minitest (5.10.2) multi_json (1.12.1) + multi_xml (0.6.0) multipart-post (2.0.0) nokogiri (1.7.2) mini_portile2 (~> 2.1.0) @@ -139,6 +142,7 @@ DEPENDENCIES bundler (~> 1.15) byebug google-cloud-storage + httparty rake sqlite3 diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb index 26c116712b..3336c4ebc3 100644 --- a/lib/active_storage/blob.rb +++ b/lib/active_storage/blob.rb @@ -25,6 +25,10 @@ def build_after_upload(io:, filename:, content_type: nil, metadata: nil) def create_after_upload!(io:, filename:, content_type: nil, metadata: nil) build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!) end + + def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: nil, metadata: nil) + create! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata + end end # We can't wait until the record is first saved to have a key for it @@ -40,6 +44,10 @@ def url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename end + def url_for_direct_upload(expires_in: 5.minutes) + service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size + end + def upload(io) self.checksum = compute_checksum_in_chunks(io) diff --git a/lib/active_storage/direct_uploads_controller.rb b/lib/active_storage/direct_uploads_controller.rb new file mode 100644 index 0000000000..99ff27f903 --- /dev/null +++ b/lib/active_storage/direct_uploads_controller.rb @@ -0,0 +1,14 @@ +require "action_controller" +require "active_storage/blob" + +class ActiveStorage::DirectUploadsController < ActionController::Base + def create + blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args) + render json: { url: blob.url_for_direct_upload, sgid: blob.to_sgid.to_param } + end + + private + def blob_args + params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys + end +end diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index adcf42ee58..c251f522c6 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -16,10 +16,11 @@ class Engine < Rails::Engine # :nodoc: initializer "active_storage.routes" do require "active_storage/disk_controller" + require "active_storage/direct_uploads_controller" config.after_initialize do |app| app.routes.prepend do - get "/rails/blobs/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob + eval(File.read(File.expand_path("../routes.rb", __FILE__))) end end end diff --git a/lib/active_storage/routes.rb b/lib/active_storage/routes.rb new file mode 100644 index 0000000000..748427a776 --- /dev/null +++ b/lib/active_storage/routes.rb @@ -0,0 +1,2 @@ +get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob +post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index f50849b694..d0d4362006 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -78,6 +78,10 @@ def url(key, expires_in:, disposition:, filename:) raise NotImplementedError end + def url_for_direct_upload(key, expires_in:, content_type:, content_length:) + raise NotImplementedError + end + private def instrument(operation, key, payload = {}, &block) ActiveSupport::Notifications.instrument( diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index e2d9191189..87fc06c799 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -59,7 +59,7 @@ def url(key, expires_in:, disposition:, filename:) if defined?(Rails) && defined?(Rails.application) Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition, filename: filename) else - "/rails/blobs/#{verified_key_with_expiration}/#{filename}?disposition=#{disposition}" + "/rails/active_storage/disk/#{verified_key_with_expiration}/#{filename}?disposition=#{disposition}" end payload[:url] = generated_url diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 53890751ee..c3b6688bb9 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -56,6 +56,17 @@ def url(key, expires_in:, disposition:, filename:) end end + def url_for_direct_upload(key, expires_in:, content_type:, content_length:) + instrument :url, key do |payload| + generated_url = object_for(key).presigned_url :put, expires_in: expires_in, + content_type: content_type, content_length: content_length + + payload[:url] = generated_url + + generated_url + end + end + private def object_for(key) bucket.object(key) diff --git a/test/blob_test.rb b/test/blob_test.rb index ac9ca37487..60cf5426a8 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -23,6 +23,6 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase private def expected_url_for(blob, disposition: :inline) - "/rails/blobs/#{ActiveStorage::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}/#{blob.filename}?disposition=#{disposition}" + "/rails/active_storage/disk/#{ActiveStorage::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}/#{blob.filename}?disposition=#{disposition}" end end diff --git a/test/direct_uploads_controller_test.rb b/test/direct_uploads_controller_test.rb new file mode 100644 index 0000000000..bed985148e --- /dev/null +++ b/test/direct_uploads_controller_test.rb @@ -0,0 +1,36 @@ +require "test_helper" +require "database/setup" + +require "action_controller" +require "action_controller/test_case" + +require "active_storage/direct_uploads_controller" + +if SERVICE_CONFIGURATIONS[:s3] + class ActiveStorage::DirectUploadsControllerTest < ActionController::TestCase + setup do + @blob = create_blob + @routes = Routes + @controller = ActiveStorage::DirectUploadsController.new + + @old_service = ActiveStorage::Blob.service + ActiveStorage::Blob.service = ActiveStorage::Service.configure(:s3, SERVICE_CONFIGURATIONS) + end + + teardown do + ActiveStorage::Blob.service = @old_service + end + + test "creating new direct upload" do + post :create, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } + + details = JSON.parse(@response.body) + + assert_match /rails-activestorage\.s3.amazonaws\.com/, details["url"] + assert_equal "hello.txt", GlobalID::Locator.locate_signed(details["sgid"]).filename.to_s + end + end +else + puts "Skipping Direct Upload tests because no S3 configuration was supplied" +end diff --git a/test/disk_controller_test.rb b/test/disk_controller_test.rb index 119ee5828f..834ad1bfd9 100644 --- a/test/disk_controller_test.rb +++ b/test/disk_controller_test.rb @@ -1,19 +1,10 @@ require "test_helper" require "database/setup" -require "action_controller" -require "action_controller/test_case" - require "active_storage/disk_controller" require "active_storage/verified_key_with_expiration" class ActiveStorage::DiskControllerTest < ActionController::TestCase - Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| - routes.draw do - get "/rails/blobs/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob - end - end - setup do @blob = create_blob @routes = Routes diff --git a/test/service/disk_service_test.rb b/test/service/disk_service_test.rb index c5404f55e6..565acbf516 100644 --- a/test/service/disk_service_test.rb +++ b/test/service/disk_service_test.rb @@ -7,7 +7,7 @@ class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase include ActiveStorage::Service::SharedServiceTests test "url generation" do - assert_match /rails\/blobs\/.*\/avatar\.png\?disposition=inline/, + assert_match /rails\/active_storage\/disk\/.*\/avatar\.png\?disposition=inline/, @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") end end diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index 3e1838e393..167aa78a17 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -1,4 +1,6 @@ require "service/shared_service_tests" +require "httparty" +require "uri" if SERVICE_CONFIGURATIONS[:s3] class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase @@ -6,6 +8,35 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase include ActiveStorage::Service::SharedServiceTests + test "direct upload" do + # FIXME: This test is failing because of a mismatched request signature, but it works in the browser. + skip + + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + direct_upload_url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size) + + url = URI.parse(direct_upload_url).to_s.split("?").first + query = CGI::parse(URI.parse(direct_upload_url).query).collect { |(k, v)| [ k, v.first ] }.to_h + + HTTParty.post( + url, + query: query, + body: data, + headers: { + "Content-Type": "text/plain", + "Origin": "http://localhost:3000" + }, + debug_output: STDOUT + ) + + assert_equal data, @service.download(key) + ensure + @service.delete key + end + end + test "signed URL generation" do assert_match /rails-activestorage\.s3\.amazonaws\.com.*response-content-disposition=inline.*avatar\.png/, @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb index e799c24c35..ad6a9dea7f 100644 --- a/test/service/shared_service_tests.rb +++ b/test/service/shared_service_tests.rb @@ -1,13 +1,5 @@ require "test_helper" require "active_support/core_ext/securerandom" -require "yaml" - -SERVICE_CONFIGURATIONS = begin - YAML.load_file(File.expand_path("../configurations.yml", __FILE__)).deep_symbolize_keys -rescue Errno::ENOENT - puts "Missing service configuration file in test/service/configurations.yml" - {} -end module ActiveStorage::Service::SharedServiceTests extend ActiveSupport::Concern diff --git a/test/test_helper.rb b/test/test_helper.rb index b67296a659..ca1e0cad7e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,6 +5,17 @@ require "byebug" require "active_storage" + +require "active_storage/service" +require "yaml" +SERVICE_CONFIGURATIONS = begin + YAML.load_file(File.expand_path("../service/configurations.yml", __FILE__)).deep_symbolize_keys +rescue Errno::ENOENT + puts "Missing service configuration file in test/service/configurations.yml" + {} +end + + require "active_storage/service/disk_service" ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: File.join(Dir.tmpdir, "active_storage")) ActiveStorage::Service.logger = ActiveSupport::Logger.new(STDOUT) @@ -19,6 +30,16 @@ def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text end end +require "action_controller" +require "action_controller/test_case" + +class ActionController::TestCase + Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| + routes.draw do + eval(File.read(File.expand_path("../../lib/active_storage/routes.rb", __FILE__))) + end + end +end require "active_storage/attached" ActiveRecord::Base.send :extend, ActiveStorage::Attached::Macros @@ -26,3 +47,4 @@ def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text require "global_id" GlobalID.app = "ActiveStorageExampleApp" ActiveRecord::Base.send :include, GlobalID::Identification +SignedGlobalID.verifier = ActiveStorage::VerifiedKeyWithExpiration.verifier \ No newline at end of file From f2f5c7979022863d02c706b685ee1233e5fdf5bb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 18:16:06 +0200 Subject: [PATCH 151/289] Accept sgids for existing blobs created via direct upload as an attachable --- lib/active_storage/attached.rb | 4 ++++ test/attachments_test.rb | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lib/active_storage/attached.rb b/lib/active_storage/attached.rb index 7475c38999..d5ded51e2b 100644 --- a/lib/active_storage/attached.rb +++ b/lib/active_storage/attached.rb @@ -4,6 +4,8 @@ require "action_dispatch/http/upload" require "active_support/core_ext/module/delegation" +require "global_id/locator" + class ActiveStorage::Attached attr_reader :name, :record @@ -23,6 +25,8 @@ def create_blob_from(attachable) content_type: attachable.content_type when Hash ActiveStorage::Blob.create_after_upload!(attachable) + when String + GlobalID::Locator.locate_signed(attachable) else nil end diff --git a/test/attachments_test.rb b/test/attachments_test.rb index 33bbff716d..ec7e9fcd5b 100644 --- a/test/attachments_test.rb +++ b/test/attachments_test.rb @@ -25,6 +25,11 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase assert_equal "funky.jpg", @user.avatar.filename.to_s end + test "attach existing sgid blob" do + @user.avatar.attach create_blob(filename: "funky.jpg").to_sgid.to_s + assert_equal "funky.jpg", @user.avatar.filename.to_s + end + test "attach new blob" do @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" assert_equal "town.jpg", @user.avatar.filename.to_s From 18720bc8fbf269184ac6f183a82ed045cc0c1965 Mon Sep 17 00:00:00 2001 From: Dino Maric Date: Sun, 9 Jul 2017 18:31:27 +0200 Subject: [PATCH 152/289] Add basic tests to the Configurator#build (#28) --- test/service/configurator_test.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 test/service/configurator_test.rb diff --git a/test/service/configurator_test.rb b/test/service/configurator_test.rb new file mode 100644 index 0000000000..f033fc7d20 --- /dev/null +++ b/test/service/configurator_test.rb @@ -0,0 +1,15 @@ +require "service/shared_service_tests" + +class ActiveStorage::Service::ConfiguratorTest < ActiveSupport::TestCase + test "builds correct service instance based on service name" do + service = ActiveStorage::Service::Configurator.build(:s3, SERVICE_CONFIGURATIONS) + assert_instance_of ActiveStorage::Service::S3Service, service + end + + test "raises error when passing non-existent service name" do + assert_raise RuntimeError do + ActiveStorage::Service::Configurator.build(:bigfoot, SERVICE_CONFIGURATIONS) + end + end +end + From bb2d7fcbf58d621e1a607df01aab5ff232255a88 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sun, 9 Jul 2017 13:22:20 -0700 Subject: [PATCH 153/289] Tests: Dir.mktmpdir neatly wraps up tmpdir + join --- test/service/disk_service_test.rb | 1 - test/service/mirror_service_test.rb | 5 ++--- test/test_helper.rb | 5 +++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/service/disk_service_test.rb b/test/service/disk_service_test.rb index 565acbf516..f7752b25ef 100644 --- a/test/service/disk_service_test.rb +++ b/test/service/disk_service_test.rb @@ -1,4 +1,3 @@ -require "tmpdir" require "service/shared_service_tests" class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase diff --git a/test/service/mirror_service_test.rb b/test/service/mirror_service_test.rb index 6fee3cadd2..8bda01f169 100644 --- a/test/service/mirror_service_test.rb +++ b/test/service/mirror_service_test.rb @@ -1,16 +1,15 @@ -require "tmpdir" require "service/shared_service_tests" class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase mirror_config = (1..3).map do |i| [ "mirror_#{i}", service: "Disk", - root: File.join(Dir.tmpdir, "active_storage_mirror_#{i}") ] + root: Dir.mktmpdir("active_storage_tests_mirror_#{i}") ] end.to_h config = mirror_config.merge \ mirror: { service: "Mirror", primary: 'primary', mirrors: mirror_config.keys }, - primary: { service: "Disk", root: File.join(Dir.tmpdir, "active_storage") } + primary: { service: "Disk", root: Dir.mktmpdir("active_storage_tests_primary") } SERVICE = ActiveStorage::Service.configure :mirror, config diff --git a/test/test_helper.rb b/test/test_helper.rb index ca1e0cad7e..03593b12c7 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -17,7 +17,8 @@ require "active_storage/service/disk_service" -ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: File.join(Dir.tmpdir, "active_storage")) +require "tmpdir" +ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: Dir.mktmpdir("active_storage_tests")) ActiveStorage::Service.logger = ActiveSupport::Logger.new(STDOUT) require "active_storage/verified_key_with_expiration" @@ -47,4 +48,4 @@ class ActionController::TestCase require "global_id" GlobalID.app = "ActiveStorageExampleApp" ActiveRecord::Base.send :include, GlobalID::Identification -SignedGlobalID.verifier = ActiveStorage::VerifiedKeyWithExpiration.verifier \ No newline at end of file +SignedGlobalID.verifier = ActiveStorage::VerifiedKeyWithExpiration.verifier From 4994e1aedd09bec450da04a3faf1188e4480906a Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sun, 9 Jul 2017 13:21:14 -0700 Subject: [PATCH 154/289] Configurator tests: work against test-local config So tests pass when service configs aren't set up. References #28 --- test/service/configurator_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/service/configurator_test.rb b/test/service/configurator_test.rb index f033fc7d20..f8e4dccc9c 100644 --- a/test/service/configurator_test.rb +++ b/test/service/configurator_test.rb @@ -2,13 +2,13 @@ class ActiveStorage::Service::ConfiguratorTest < ActiveSupport::TestCase test "builds correct service instance based on service name" do - service = ActiveStorage::Service::Configurator.build(:s3, SERVICE_CONFIGURATIONS) - assert_instance_of ActiveStorage::Service::S3Service, service + service = ActiveStorage::Service::Configurator.build(:foo, foo: { service: "Disk", root: "path" }) + assert_instance_of ActiveStorage::Service::DiskService, service end test "raises error when passing non-existent service name" do assert_raise RuntimeError do - ActiveStorage::Service::Configurator.build(:bigfoot, SERVICE_CONFIGURATIONS) + ActiveStorage::Service::Configurator.build(:bigfoot, {}) end end end From 9cf33478991b9fab663d5502342729b98eafa2bd Mon Sep 17 00:00:00 2001 From: Dino Maric Date: Mon, 10 Jul 2017 12:09:28 +0200 Subject: [PATCH 155/289] Scope aws-skd to version 2 (#34) Since we use new aws-sdk API, I've scoped aws-sdk version so someone doesn't accidentaly installs wrong version during the development. --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 8e2031ed85..a757a5c793 100644 --- a/Gemfile +++ b/Gemfile @@ -8,5 +8,5 @@ gem 'byebug' gem 'sqlite3' gem 'httparty' -gem 'aws-sdk', require: false +gem 'aws-sdk', '~> 2', require: false gem 'google-cloud-storage', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 797996cdc3..7e4c6f78f2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -138,7 +138,7 @@ PLATFORMS DEPENDENCIES activestorage! - aws-sdk + aws-sdk (~> 2) bundler (~> 1.15) byebug google-cloud-storage From f66a69076f43617bacfe45961e229268ed15faa7 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Mon, 10 Jul 2017 16:17:48 -0400 Subject: [PATCH 156/289] Expose chunked downloads --- lib/active_storage/blob.rb | 4 ++-- lib/active_storage/service/disk_service.rb | 8 ++++---- test/blob_test.rb | 13 +++++++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb index 3336c4ebc3..1a15361747 100644 --- a/lib/active_storage/blob.rb +++ b/lib/active_storage/blob.rb @@ -56,8 +56,8 @@ def upload(io) service.upload(key, io, checksum: checksum) end - def download - service.download key + def download(&block) + service.download key, &block end diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 87fc06c799..7e64e1e909 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -20,8 +20,8 @@ def upload(key, io, checksum: nil) def download(key) if block_given? instrument :streaming_download, key do - File.open(path_for(key)) do |file| - while data = file.binread(64.kilobytes) + File.open(path_for(key), 'rb') do |file| + while data = file.read(64.kilobytes) yield data end end @@ -55,7 +55,7 @@ def url(key, expires_in:, disposition:, filename:) instrument :url, key do |payload| verified_key_with_expiration = ActiveStorage::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) - generated_url = + generated_url = if defined?(Rails) && defined?(Rails.application) Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition, filename: filename) else @@ -63,7 +63,7 @@ def url(key, expires_in:, disposition:, filename:) end payload[:url] = generated_url - + generated_url end end diff --git a/test/blob_test.rb b/test/blob_test.rb index 60cf5426a8..cf27d59348 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -12,6 +12,19 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase assert_equal Digest::MD5.base64digest(data), blob.checksum end + test "download yields chunks" do + blob = create_blob data: 'a' * 75.kilobytes + chunks = [] + + blob.download do |chunk| + chunks << chunk + end + + assert_equal 2, chunks.size + assert_equal 'a' * 64.kilobytes, chunks.first + assert_equal 'a' * 11.kilobytes, chunks.second + end + test "urls expiring in 5 minutes" do blob = create_blob From 53d5384ac265ead44b25eec5f8f5020568184da2 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Tue, 11 Jul 2017 10:59:36 -0400 Subject: [PATCH 157/289] Depend on Rails > 5.1 --- activestorage.gemspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activestorage.gemspec b/activestorage.gemspec index ce366d60c2..a10cc6336f 100644 --- a/activestorage.gemspec +++ b/activestorage.gemspec @@ -9,10 +9,10 @@ s.required_ruby_version = ">= 2.3.0" - s.add_dependency "activesupport", ">= 5.1" - s.add_dependency "activerecord", ">= 5.1" - s.add_dependency "actionpack", ">= 5.1" - s.add_dependency "activejob", ">= 5.1" + s.add_dependency "activesupport", "> 5.1" + s.add_dependency "activerecord", "> 5.1" + s.add_dependency "actionpack", "> 5.1" + s.add_dependency "activejob", "> 5.1" s.add_development_dependency "bundler", "~> 1.15" From f28f4e9e4071ee7a33cb343b966f1c8d48d9437e Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Tue, 11 Jul 2017 12:40:58 -0400 Subject: [PATCH 158/289] Revert "Depend on Rails > 5.1" This reverts commit 53d5384ac265ead44b25eec5f8f5020568184da2. --- activestorage.gemspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activestorage.gemspec b/activestorage.gemspec index a10cc6336f..ce366d60c2 100644 --- a/activestorage.gemspec +++ b/activestorage.gemspec @@ -9,10 +9,10 @@ s.required_ruby_version = ">= 2.3.0" - s.add_dependency "activesupport", "> 5.1" - s.add_dependency "activerecord", "> 5.1" - s.add_dependency "actionpack", "> 5.1" - s.add_dependency "activejob", "> 5.1" + s.add_dependency "activesupport", ">= 5.1" + s.add_dependency "activerecord", ">= 5.1" + s.add_dependency "actionpack", ">= 5.1" + s.add_dependency "activejob", ">= 5.1" s.add_development_dependency "bundler", "~> 1.15" From 1966c188cfb06b39a47082e2f6c6e33a43668ae5 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 11 Jul 2017 18:53:17 +0200 Subject: [PATCH 159/289] Very incomplete first stab --- Gemfile | 1 + Gemfile.lock | 2 + .../controllers/variants_controller.rb | 22 ++++++++ lib/active_storage/engine.rb | 53 +++++++++++------- lib/active_storage/routes.rb | 1 + lib/active_storage/variant.rb | 52 +++++++++++++++++ .../verified_key_with_expiration.rb | 2 +- test/fixtures/files/racecar.jpg | Bin 0 -> 1124062 bytes test/test_helper.rb | 3 + test/variation_test.rb | 16 ++++++ 10 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 lib/active_storage/controllers/variants_controller.rb create mode 100644 lib/active_storage/variant.rb create mode 100644 test/fixtures/files/racecar.jpg create mode 100644 test/variation_test.rb diff --git a/Gemfile b/Gemfile index a757a5c793..c4ecf50bbe 100644 --- a/Gemfile +++ b/Gemfile @@ -10,3 +10,4 @@ gem 'httparty' gem 'aws-sdk', '~> 2', require: false gem 'google-cloud-storage', require: false +gem 'mini_magick' diff --git a/Gemfile.lock b/Gemfile.lock index 7e4c6f78f2..8d0c4c7937 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -99,6 +99,7 @@ GEM mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) + mini_magick (4.8.0) mini_portile2 (2.1.0) minitest (5.10.2) multi_json (1.12.1) @@ -143,6 +144,7 @@ DEPENDENCIES byebug google-cloud-storage httparty + mini_magick rake sqlite3 diff --git a/lib/active_storage/controllers/variants_controller.rb b/lib/active_storage/controllers/variants_controller.rb new file mode 100644 index 0000000000..24cee16e80 --- /dev/null +++ b/lib/active_storage/controllers/variants_controller.rb @@ -0,0 +1,22 @@ +require "action_controller" +require "active_storage/blob" + +class ActiveStorage::Controllers::VariantsController < ActionController::Base + def show + if blob_key = decode_verified_key + variant = ActiveStorage::Variant.lookup(blob_key: blob_key, variation_key: params[:variation_key]) + redirect_to variant.url + else + head :not_found + end + end + + private + def decode_verified_key + ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key]) + end + + def disposition_param + params[:disposition].presence_in(%w( inline attachment )) || 'inline' + end +end diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index c251f522c6..8918b179e0 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -33,29 +33,42 @@ class Engine < Rails::Engine # :nodoc: end end - config.after_initialize do |app| - if config_choice = app.config.active_storage.service - config_file = Pathname.new(Rails.root.join("config/storage_services.yml")) - raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? + initializer "active_storage.verifiers" do + require "active_storage/verified_key_with_expiration" + require "active_storage/variant" - require "yaml" - require "erb" + config.after_initialize do |app| + ActiveStorage::VerifiedKeyWithExpiration.verifier = \ + ActiveStorage::Variant.verifier = \ + Rails.application.message_verifier('ActiveStorage') + end + end - configs = - begin - YAML.load(ERB.new(config_file.read).result) || {} - rescue Psych::SyntaxError => e - raise "YAML syntax error occurred while parsing #{config_file}. " \ - "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ - "Error: #{e.message}" - end + initializer "active_storage.services" do + config.after_initialize do |app| + if config_choice = app.config.active_storage.service + config_file = Pathname.new(Rails.root.join("config/storage_services.yml")) + raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? - ActiveStorage::Blob.service = - begin - ActiveStorage::Service.configure config_choice, configs - rescue => e - raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace - end + require "yaml" + require "erb" + + configs = + begin + YAML.load(ERB.new(config_file.read).result) || {} + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{config_file}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + end + + ActiveStorage::Blob.service = + begin + ActiveStorage::Service.configure config_choice, configs + rescue => e + raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace + end + end end end end diff --git a/lib/active_storage/routes.rb b/lib/active_storage/routes.rb index 748427a776..fade234ad3 100644 --- a/lib/active_storage/routes.rb +++ b/lib/active_storage/routes.rb @@ -1,2 +1,3 @@ get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob +get "/rails/active_storage/variants/:encoded_key/:encoded_transformation/*filename" => "active_storage/controllers/variants#show", as: :rails_blob_variant post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb new file mode 100644 index 0000000000..9b9dad43da --- /dev/null +++ b/lib/active_storage/variant.rb @@ -0,0 +1,52 @@ +require "active_storage/blob" +require "mini_magick" + +class ActiveStorage::Variant + class_attribute :verifier + + attr_reader :blob, :variation + delegate :service, to: :blob + + def self.lookup(blob_key:, variation_key:) + new ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(variation_key) + end + + def self.encode_key(variation) + verifier.generate(variation) + end + + def initialize(blob, variation:) + @blob, @variation = blob, variation + end + + def url(expires_in: 5.minutes, disposition: :inline) + perform unless exist? + service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename + end + + def key + verifier.generate(variation) + end + + private + def perform + upload_variant transform(download_blob) + end + + def download_blob + service.download(blob.key) + end + + def upload_variant(variation) + service.upload key, variation + end + + def transform(io) + # FIXME: Actually do a variant based on the variation + File.open MiniMagick::Image.read(io).resize("500x500").path + end + + def exist? + service.exist?(key) + end +end diff --git a/lib/active_storage/verified_key_with_expiration.rb b/lib/active_storage/verified_key_with_expiration.rb index 8708106735..4a46483db5 100644 --- a/lib/active_storage/verified_key_with_expiration.rb +++ b/lib/active_storage/verified_key_with_expiration.rb @@ -1,5 +1,5 @@ class ActiveStorage::VerifiedKeyWithExpiration - class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveStorage') : nil + class_attribute :verifier class << self def encode(key, expires_in: nil) diff --git a/test/fixtures/files/racecar.jpg b/test/fixtures/files/racecar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..934b4caa2270473998c2f5e75cece9cb86f5080b GIT binary patch literal 1124062 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⩔LEz1z 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

r8PCFnKDBVofo>l$$diK`6MTB6`F2&mZhW(GMmP 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

-J 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 z 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%0D(1 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%0D*G(*Kw8w+who0 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≺8KnAN~s* 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∓8JXrc@LqtQ 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

XBWLrQD9P9Gu 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 z⪼v5ge18o>CaKHcfKVvR$no}p56x7MUQFxWl2MK1@ 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

MMLxjjxM@8zCH^Qlv(^g&ZYGiWbc z#uKEpQuv3SCLLD`sfwx5_*%ZlS*wDBl|NeA7*|SBObHHcyT<6z^CTyKhn=~ay)V}sqzQ!_+ zO4BXlY}tkbrFBxL@`{cpzr&|pJ#jK){EOK6RsP>w5#q>&KIxF`E5~Y@Cvtc&h_;z&|b-jse3zSQ03I*_4bkEtVLgBFDiESNKT0J zzwn{%LC!Hp^#doL%?o~_bym4}Dl|V>%RJ%q@^MQhh{I31!xU|%&nM2$p8-oifG}1Z8H`MzldQQo{8EzcsLCsz4l<3S|&bQU%1go8u|D19UW{Tvj zUVG%ev4*F~t?XFbnKPP?wM+6Jd21`3SYF*mjeKlfWIm}NElw>cFsOCvl%{yyS=(dX z?8EvX-_?7MRYz$S?yGVy3=UJNgUxaKla`)I{ouVbOiZeYQlg%^d(55ql`g5|>C)F9 zobu!!>Z&Pis7`Tzli8Y6-II~)rxtbObgO6nl4cdz155ljdnFRP$Miw3a*f9rs?tCB z7`J*+Z=TM&Hlw@oT#AMH$r$yh(K9I&W9@Dn zeZIt&eK^zDh>J&0FM1rjSZ%Az{uNG{)UCN8GkE?@IqJlKOS^7M zoeJ>A95I-Dv?wix;%7!Y$s#Upz8Ngzx!K(1djPqx-SP-4sKw!t_u2(*9-j8nw++AQ zF7=+hN`AHeGq+t=T}~d#J84nrM`l&09?Z)RyT#+>TvxK!Bc`4{kEi*Zx_c>bU3J#h zkmD`mdncYan6SGidsg1e<4h+*%;6OYp|h4)9C4XZP4@h5f5K^x#!Ot7-cjpD%#=0R z1=D?1;;aLm)OWY&*ZM0pd&w?dINWYKO)slMyzwfR$BVb7 z?y6Co07YbDv$NT4OG--%SmigbtucO?lxA-+ZE}Im*tI*kGw!XW>8+pJG@i+Pioc{8Q&a;{R7$KOv(P&)teJ$DC1E2_z-9KYw&m#QCA`=I<% z^$Utms~2zQgC33RQjK987j^Ky=1?@rNlE-R;Yh@C>FGA*o;Npb)g-G1l(K&)hc#8W zn|0K(g4>NQcUqXAe)RcJ$!WE#UUpfB$i89Q{#x>8{E2Q#O7&z8-J!rn4xjqtY!7Mm zjgrz7yS@6ExhFI}l>F_a`Qna`M#$L1M|DomXY&Y-mwm36`fPP=%dmC0w8PjOLS!(x-125&GO!PKA3IGl=v;w=V zxZSCN^<`bytaR@_Xyx)-Ug2*WrOq&zq<343b~{UKrY-$6A^-dVRf8@?$JU&6 z-}l^Y;1wktYtC*sdPOv4^L}QKom$u0+*G|*Y~mr+=&pNLO|Nq7$a0@@R!#6yK9nf( zg%Oh+nH_NAN7-N7muKHG-?70e%W~`|1=o)`$@xAWFQ&v7=w*h5P9;rNRf$gwldxu6r*o*S*}qm?918NkwTcrKpTiD3WGM zWr}2okU2v{5x#xyHJ0A@eV*_Cf4|@N`*m{9S$nOu*Is+=wfEU)?Xyp#6{B^K#Cl^h zV~7hJid>2iH2PV(&)SW~q%quRUI>gb64Ed*v*hK4;Nyi1Hw1A*JTlzskRTd~Qb($x z)MdEWLLz7+28+V1L86dI8E$Jx#E?$%MVOGC88oU4w;!|zW=1&B=oBKth)QJ9@l*l{ zfpf!qdXZ^Vh8x*S)P}~Q5)pQ8bP`DsVL?Jz;;9T#9GOmFk(mf02|>XlZ1Hra+Y~Aa z$#g>$Ay69NvYtgHA>dL(+1hI%rf7f=mEo>|W+AoM|By&kHBC;fma%Iku|KuY=+I3I z=nHRPUajG)PIis8U>cVQI7z?&oPtEW=wvF>ipBI|F+q|K##u9%w$3yfh0S3`Ws*o# zmM2VrVfc!p(BZT=JFhL-59Tl+GvPcinIn@#-+=cd*%>+70n{7_2jL+qL<2trM24&( z6r>3u!9N7VfJhJ>;79<14~|%CI*sOHOJcITteo8m06hz2q5}am$OUAw1?)_S1$nWv z2zyO%gEU)j0}7KmruQso7Ma2%Q`sT`U5L$%v)pXXv6(h3gIfulCha0M#V%G}Ot?t_ zeLl>I^I}rRs|rlvOrJ>DxH2p!5;!^)H<_R^CleOV6vhN)gDcZ#B4Ol7F`P&MYfp+c zAb7Zr8G>Cs2(mFTzyWP|V3ll$2n3PFa$ZNPgOlvTgiiehXF&Ot&wx&}v!^nR6>KQ5 z2TU9Wz_Dk-+b}3hHs0EgvX1_6oL|_7L}1eBhIl3(t^r#~YgcQ=xbMI)%nPot+K>gw zg(K#tV(ma5ovBz`0)>OOrW4j}h9$5M2?Cww<={pFjSKvkOm!VMpBPNA0XiAbm`s`{ zg+_H9%Q9=60;@0uFFKC5A-lRwC5Vg@fNm4~aF62o%+UcJWq{Xlk6+onZ8@7JHdZLO z=mkR@e-mZn;PFS6&AD)Tnx~hY7Gj^liHn_U1?-y;Bs|t{fy1&j_{zqbLMkpUfIiD z2I6;y?+A>a`Z0lt0yK8DVCSFkFo68YLm`6+Pv)p$GvTQ0D)TcaWCDq?iDChFHLhvp z@UdwCEd_o8?1Y)&l(Ga|=`@ztG!#FLPIe_vjG{)iuyiXn4#*J2vzRm!5|u>9Gf6~% zfb9Ie#%~qE9A+2;Q_MVF5#aK_U4I@HoicXAnPU9u7)yrh_)Uq!!;hyh?eMPCxWot~ z3dN4($24P@+F4qR4`_np=xNMCZZvv;K85T$ZYgn229$2X)7eFwU2!xDjXuS=k{tO4CKCjZ1=>KP!nPJ-(!9X!oI#p8f(cW=Q1=UVmNR>3 z`~@!pn%M1Uc6N_j2^R$J#K4{Py4f&iM4Q8gVHf6N^T8aD#0Z=Qzsum~0l+#4$_KDS zJ?NWUqZOl{p;`Jwnls4>X9?1skQh4!5rZMXJX!_qgJuZ|3JM9%5)zsvDIzQ)Iahqv zEb+MuX3w5Gd-ej!S?pu%!b$vm<`NYV5fu}a5EGM_BPJ#`2R@0-;bf8gPXvrUgd_!l z@|StIaYI;8`Zun6rUuQJu;7oP*C;xe9>H}$KVIJo*AJbgVg?_KkNB*e`PwB=>%B*4#yWvj3RCB+Lg@Rpy= zhYpFD18eZernnok)cGTQZzRrkE`g!ivy2~BKwvrcaS2f3>U^S+y&98LKcT|!HCQpJ z!e}Kli-%pNB%}xRYUz7~9@w`~pe{=;VP4-kw6d?=%?z6k?e~rfyDEd!ZPZ3QYE!?y za~-rQ3E~Ug5dWFy=*tY7s^Ea=_#pMT_veG(Tq`DRa;{=r9t{8R4nwP4oj^)2e~zxT zeY+>txkah9X8D8M(lys*-<*l)vd+{XbPK46~GYnR(-fmC!Lj-E+NV6nd=Gv7)}ZoJvUakTweW+NH!GxXGEcU>}@rSvkV^Uii(F zU*ppmXcl95WntOE^`j8o>FQ9yt2Y`a5*r55NB4<$Y1IoxJo>2hmyAqN(<=?`r4l|1BE|Xod$@p@M2k~ifmcZ+IYv@Lz z3paQq|EPb&^Fm_((n!f0A;hADQ)z=YUcS&0`J9F>lX`0qV{2-=@M#O(9zasQPh?^tNlnCwq8qW9^RHTe=c6 zhz_jqW7Y>Gc+TRd z*LtHxy<4)^d}|EbZ+tw(_Pzbf>d>2E#$DJVX7fBE!BKyaQc{u5eaY{&=Ra@jU*&}e z8HuGem^-QqE~z{l@hP3eSRS?{XWx!vL8b1u5r?acJX?n@RIMR2w}c;I#g1I<7}UGk zM;V2VY)WidH&0u30E0g+t8TdXbwz(i(4K+59pQ&xQG~+?9%;`HzQ5DSduixY@D>eC zN2@tO6+NaQDm8T@iGD#37K}n0ube(}fo@)fmGBt2o13Jptm^kF{bYKkR6DlXtmZ|} ztb~y(bC*U{9ERN8Hf5)qrCIHMX|_ycaVG72@KDL}{g*{tBF*Gu4~N*SUWPUgl{#~M zwbluqsD<0+Su^_YCGjvWuw+Glw2Ob)lCZICaK{GoRMeM*wEjbVkM^$9UY=7_6;#_& zj6q$DuqIgDOBNIDsl;VO_uA2;p1z!`HC&n-x-#5UsCrjk{l{U3$C^i)z8}0>2$mZd zYt+^b?$3=W(xWAiD6a8cS91IwZ1ODcWJQ%Lss+6AqusrGCi7y|*?YPAqfo&pWSX<4 zomRfBE48os0=;RoH-mobQr;a6{`W`Q&ChrCwWrk_9fkJwQFm-TLm4vK5B?e>91pZ7 zH<>hP8Wn_DEp6gEv@tDk_c8I|<&+B>_wBSQa1I?U?mc||aqdxQ?@hbpB72LZ3auKQ zhq`_Y7P*&?xeGvO1pL$RAw$IygK(9J|@Wt(ogUcBw0=!-l+KWa+VxKej3!G1iE9 ztXP}y*AJ)Sts}>t^WH6doldse@#DUGf%K5HXf$qzy+YZg_OP3wrIb#A)^)ngdzS6g z*pfHzK7a1rrB+|}Bqo*`B?r1!HV`g{$X0~cz7c)N=uvzxc!coCA5cHh(|>HxJUVEhXu-~4xorUivQmHe=kUCmktl&1tTptNH?Nl| zkxx5*+v(p-t;~sjYjOH%tRSf&+|(&E?({INvu7#Yz~-RP-E$2i&wF*#u!-pd<*IsD z2;%o1_WL#k>S;YQ$F#GOM#7I4=Vd+K-qU~E{Z&D}$GJ5-MF}iv(Wl4NXXzP>6hdRd6{kN@5T;By(GM}Emnmrs+>oc5yNg8Hms&L({ zplI}ODM44`D<#v*in?li2Q9PTX|sN`-P}q^Kowpxh}F(f2o;&jR-ccbf|JBTJ?t8e)qfiRT zcEzTSMQ<0M4#pl_H&mWotLaBsagdu(U;E?mvl}P6y0S+h^cF?`A$y-(Ckt*X9y4D# zEKZeDi(g)jiS=F5+Mb)s@&Kdby_DTa0@Z6xI)_fLtsda!B5HKMuk*_t*x25N%`a-h zXSwe=d3%Ep&rZ(-FXEv&8@i00Y@HBybY3g>N8+|?2NN>2-wqej-#u&>)hoFYEY)<% z^hbrKrE{ZO;mx|h7ai|qo+nrRICa(GZM#xL-1&n~vK+rOJ<$$MVjXKK7|MU~m#pdA z@P3``35-kh-9>ee>-*0rMO@9v+I7O!_56V=X*){W4D~itRi@RRwL{AYekj_Nt7*_t ze!NCijDI&DrskEt^x*50N340JWh3-Uuj`Zv46m54wrJ2Kvzn`-+0*ylgRaY=7f)4f zzZY7ytn%T~=xa{5tA?!_6}!@pRwLeDlwP+t>eER-vDEZxq*VQ)3^|W&JM;EkoJX}k zce0-(%G_@&Uth0at$(81rv9n^X3duJoVJUn=4_2#F7V7YwE!ox<;00avI!O6YyuC* z4`DZ5nI~ix-k5Wg_)u&3<;9Q=L}*G_5g)$OzS>Y~Lr&BgVf?9-(bC?4?Rk&3+-R}q zms-1r&pAPSpxfU6S*BEL$tYBu_a%34(Upe-9RiIavI&r6<*ukUwAx+o0rA7vPYG;# zy0l%({9?VMo#$~gX`GMFB}oSt(soyuiMlaEbtm?EN@tnobj&d7$*}L#@- zli+W1oj6u{?{J%AefMU?BOSLqq`CLD`WDkp-|DPhDrdVt(G0nFt-R}F^*1ncPmGB1z^tq^7YfYg zMy%9Ukm=stZ69WUyE3$Zgb29meKcQ1rX?}_DB`$?Rer>$m76-GHuGKI_^B@TP#OOH z=-Xa3!s*?CCLfaQ_m;|Cck3y3NLf<9T(>UCKC;f={)=~?(p|4iaw}77g|c3n-WPN8 zMQ#1duGgGv>E;UUQC_2V;c86D^H+(I_pUbGXwu9w9gY}} za@p4r>5+JT_w!Z8!6_?F<&Q%1Yqpm2NWV;T+@fcudqJ*{?0NoT=cd)8(7YV#lO4sD zO3K5_+s$)w-(Shz({1z)qkCXCSx+iV>$T#Kmp?KW+CGUM{yqvF*6W)m^d)V0!Eo*% z#;L9LZMAY#C~3QsoSe4Oz@n|h@FbV=^5?rx(?ZK{cV{gUxXrUL=SmVD%##VHNYR^R z7T*c*{DvjD4wFe1%0WW~5B9U#8ir2=iGRMgkg<0QVPBk;pAp4>v#9G53B44R7ataU zqx7d8e&%%Mb*>4R2D-m!+wLkN;+4Mla;ZSD-hAa9waGc8LdMFttVK_@Q!09X~@)d z>9KPsH7>4^$+ip>zgqYW%Ufx{Qp)(CeLnTkXMSmfm?@_76QnU;a%YiXXybuMZmknF z+gyS>MxoKNFHI+1bhR(b2L31+Divk99@07gwZ_&kr_mqM>w07CYWKU_)Dt@ar=wqS-VvS2j*)J7@inVT$_?CCRM#!;k(bC z#x1GIKRWlM7xqVOd`T7jBAL9ZC%Rq8Smd_(c|Aoxldpaj=lw4dZZ_>jt9TP~Q4h+0 zsAaY)1q2-6yHhr3ys7Tt;>{(3l6ANCsHa8gy%DcVCN7wzU}&t>J+w#V$Ma-8<^~74 zmUm#Z`j2MnP<&)tK0;978AJrL`hbJqW_Ncc47X{7Z5oB#zxQnQSepJdzcZCm+^FQC zcTT1|&2y0C_^Ds#45qL1x$V-(Tkp!umfFhSscd+yJb3Mc-I2nfSl;mH(f8~~$Py6C zfS~PgL<5czh(r1i5iDUigK#4PENHj^mEZo1yCfc34q}&bAVbB0EU+~qya3&1)>-L zhJVsreC#UXqngCuo#skMRQ6TS9aMcHRG=Sky5DCEA0EWXr{s1N)u~{gycmhbAbE~+Y`z-E z;T?l@0n8Q2St_srFn1)U+$}KA7#`faA-*xZJAefuIXduxvV_K9IEpPi#zO}@$0NZ? zDXfbs1Vv1NBd5SzmpO7qM?txlIXVjhK|EvdEnx@}i{a=K0ECH;!FvHL5yQy~m%(+7 z0|OA^9)saV;hBQxp8}(IMigP#J+;8Y*}zS!7}s5t`MZf4F!Y5*cGXY zL88EVG75`Qn_!w`h8ZUhBV#lY>FJ4ZQ9)ZE%xn?%ww4f*eW0ANaHN+X;u-6Qa6caJ ztIK&O`>Ha4r}kA4?_=wy2JqZta6R0};4lS0SoiVkpZM&b`0Stf?4S7TpZM&b`0Stf z?4S7TpZM&b`0Stf?4S7TpZM&b`0Stf?4S7T|NrsX(R%K6W;mRawT+drnS~KMK+BEP zH#9Kf1~Cy&@b)!$#Twzbc?&|I5DvD$Q4$Ek6Bu6l*47p<3}T3IAm*o|znmRNfl8HN zSw9i~T~e6fMP~x0AK-^3k{ARKl|cg7!YqcVZ!sp5zCQb8#rRiL$ceqMNj2Z@PrCV@9;b>zFUbLA0aqK>=+))Hyy zg(JC7gUSGwrIeiGBpR0FpR4J%WSLfnafLiRr{!QUjeLMHX3}ar2Lpa*SH*_Jpt0x#l93N6Sz)S@45kff zvTbH8bh-$@K1~3D&X`#P0@!&&$1BnCzDnRlV0c>uTxI063b5P8lSFXCQ^}Lo0$Ok~ zJCm6J1kOB-Z&J!c(`vEbLbjw4NjmZvcJ(GnV0ZK-s_=y#493kQ8B=}u& z9eH?xNC~N?gi^N~55y}Y(W>k$psBP#X=E3FILj1X4ZHD`7qDqd3yGZM23>{-_NDx% z{2VoY;{J^$Hh9>&I`W*-fE#MrlIUbSWdqC8nM4QpDItHhLNJR3iOMjey3k3e}KP<~`jU_fIUfyFQ+GnnxH zH63|Xw5F=2nmSq&tAd(fH>ThjZaA8!7tkBjmj(thpztJ_O!xG~(?M?FW6F~iF!RK_ zlEw`IcBrv45D8i?G&(3*M;?^#N>Xw6BDu0D>^`Vv2=sybC|objphu(;$V9D4r&7Zb z@h%z|tdgcG603wlspFOKSgg8|h6^60>P&J`#S%G1aRhQUEZ`|q1tX1B)r{3pIAe^u zfxe-Up`jrVgH|&{WAss~s+tpyW@)KqM&+0W)&~ewCIelFYMN*l0urr6AmBBWRFMR% zl7@z=vy!?disX!OCaI45D(IkEHh5oS&;j7ha60muXic<}p^1}&5f1ioCnUz0y>U>P zKu5EU0OtTZ0T^r}Ybv9V${ahxc+i!=?heKTSxs4eB9DU`nMpFhQ#h@NV==%^kqM$c z*r~y0n8=F4sA5z#G{!m!EZmL+nit&IS#UdIRk56AWK+Nzr;hv@RkaCuwlLecBTQo5 z@N_RKP!KpIb^>d`@+4DXt!zDMG^QK7v!Eu0vj>k2G%|x^Ln5*W9Os&neG|N`g2^Q5 zdxEVqK#j?qwr;>ZeC^oEO=5u*Qx>SfG|bHUnt*+o)SsByxS81vC@j)X+%Rfp=1pX0 zcDn+*VJ90FWY;IUgSLSk9B4XK&1p0%klA%wEvAt-gWV}K-{0nDSK_x+eQy^2w?w!` zza?6;=w9$hK55%&T3LeL3?>m%>66C!ZM|n=`L+5pas6tAnYey2#!O5<+hiu5pG`9p z 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 WOi1+kaupts Date: Fri, 21 Jul 2017 16:45:55 -0500 Subject: [PATCH 210/289] Test actual transformation via controller too --- test/controllers/variants_controller.rb | 1 + test/test_helper.rb | 6 ++++++ test/variant_test.rb | 7 ------- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/controllers/variants_controller.rb b/test/controllers/variants_controller.rb index 22f5ec1454..6753584d4d 100644 --- a/test/controllers/variants_controller.rb +++ b/test/controllers/variants_controller.rb @@ -19,5 +19,6 @@ class ActiveStorage::VariantsControllerTest < ActionController::TestCase variation_key: ActiveStorage::Variation.encode(resize: "100x100") } assert_redirected_to /racecar.jpg\?disposition=inline/ + assert_same_image "racecar-100x100.jpg", @blob.variant(resize: "100x100") end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 69ba76b9c4..20b22049b3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -39,6 +39,12 @@ def create_image_blob(filename: "racecar.jpg", content_type: "image/jpeg") io: File.open(File.expand_path("../fixtures/files/#{filename}", __FILE__)), filename: filename, content_type: content_type end + + def assert_same_image(fixture_filename, variant) + assert_equal \ + File.binread(File.expand_path("../fixtures/files/#{fixture_filename}", __FILE__)), + File.binread(variant.service.send(:path_for, variant.key)) + end end require "action_controller" diff --git a/test/variant_test.rb b/test/variant_test.rb index e41842a80c..5294b87135 100644 --- a/test/variant_test.rb +++ b/test/variant_test.rb @@ -20,11 +20,4 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase assert_match /racecar.jpg/, variant.url assert_same_image "racecar-100x100-monochrome.jpg", variant end - - private - def assert_same_image(fixture_filename, variant) - assert_equal \ - File.binread(File.expand_path("../fixtures/files/#{fixture_filename}", __FILE__)), - File.binread(variant.service.send(:path_for, variant.key)) - end end From 2e9ff80e50fee0df6ea47d4d43a27c4505985b29 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:49:00 -0500 Subject: [PATCH 211/289] Quick example of variants --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 625b960624..48a33f8b3d 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,13 @@ class MessagesController < ApplicationController end ``` +Variation of image attachment: + +```erb +<%# Hitting the variant URL will lazy transform the original blob and then redirect to its new service location %> +<%= image_tag url_for(user.avatar.variant(resize: "100x100")) %> +``` + ## Installation 1. Add `require "active_storage"` to config/application.rb, after `require "rails/all"` line. From 6ac4fec964e67cf3d7dfbf7726bff9b05aca522c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:50:23 -0500 Subject: [PATCH 212/289] Mention need for mini_magick with variants --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 69166664f6..b56999cae7 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Variation of image attachment: 3. Run `rails activestorage:install` to create needed directories, migrations, and configuration. 4. Configure the storage service in `config/environments/*` with `config.active_storage.service = :local` that references the services configured in `config/storage_services.yml`. +5. Optional: Add `gem "mini_magick"` to your Gemfile if you want to use variants. ## Todos From b44b0f2c3b61beefbf5bcaadbf74f70137ded52e Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sat, 22 Jul 2017 13:14:46 +0900 Subject: [PATCH 213/289] Fix RuboCop offenses and warnings --- .rubocop.yml | 158 +++++++++--------- Gemfile | 2 +- .../active_storage/variants_controller.rb | 2 +- config/routes.rb | 2 +- lib/active_storage/engine.rb | 2 +- lib/active_storage/variation.rb | 2 +- test/variant_test.rb | 6 +- 7 files changed, 87 insertions(+), 87 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 7b4478d3bd..452e1b1e7f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,6 +8,85 @@ AllCops: - '**/vendor/**/*' - 'actionpack/lib/action_dispatch/journey/parser.rb' +# Align `when` with `case`. +Layout/CaseIndentation: + Enabled: true + +# Align comments with method definitions. +Layout/CommentIndentation: + Enabled: true + +# No extra empty lines. +Layout/EmptyLines: + Enabled: false + +# In a regular class definition, no empty lines around the body. +Layout/EmptyLinesAroundClassBody: + Enabled: true + +# In a regular method definition, no empty lines around the body. +Layout/EmptyLinesAroundMethodBody: + Enabled: true + +# In a regular module definition, no empty lines around the body. +Layout/EmptyLinesAroundModuleBody: + Enabled: true + +# Method definitions after `private` or `protected` isolated calls need one +# extra level of indentation. +Layout/IndentationConsistency: + Enabled: true + EnforcedStyle: rails + +# Two spaces, no tabs (for indentation). +Layout/IndentationWidth: + Enabled: true + +Layout/SpaceAfterColon: + Enabled: true + +Layout/SpaceAfterComma: + Enabled: true + +Layout/SpaceAroundEqualsInParameterDefault: + Enabled: true + +Layout/SpaceAroundKeyword: + Enabled: true + +Layout/SpaceAroundOperators: + Enabled: true + +Layout/SpaceBeforeFirstArg: + Enabled: true + +# Use `foo {}` not `foo{}`. +Layout/SpaceBeforeBlockBraces: + Enabled: true + +# Use `foo { bar }` not `foo {bar}`. +Layout/SpaceInsideBlockBraces: + Enabled: true + +# Use `{ a: 1 }` not `{a:1}`. +Layout/SpaceInsideHashLiteralBraces: + Enabled: true + +Layout/SpaceInsideParens: + Enabled: true + +# Detect hard tabs, no hard tabs. +Layout/Tab: + Enabled: true + +# Blank lines should not have any spaces. +Layout/TrailingBlankLines: + Enabled: true + +# No trailing whitespace. +Layout/TrailingWhitespace: + Enabled: true + # Prefer &&/|| over and/or. Style/AndOr: Enabled: true @@ -18,98 +97,19 @@ Style/BracesAroundHashParameters: Enabled: true EnforcedStyle: context_dependent -# Align `when` with `case`. -Style/CaseIndentation: - Enabled: true - -# Align comments with method definitions. -Style/CommentIndentation: - Enabled: true - -# No extra empty lines. -Style/EmptyLines: - Enabled: false - -# In a regular class definition, no empty lines around the body. -Style/EmptyLinesAroundClassBody: - Enabled: true - -# In a regular method definition, no empty lines around the body. -Style/EmptyLinesAroundMethodBody: - Enabled: true - -# In a regular module definition, no empty lines around the body. -Style/EmptyLinesAroundModuleBody: - Enabled: true - # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. Style/HashSyntax: Enabled: true -# Method definitions after `private` or `protected` isolated calls need one -# extra level of indentation. -Style/IndentationConsistency: - Enabled: true - EnforcedStyle: rails - -# Two spaces, no tabs (for indentation). -Style/IndentationWidth: - Enabled: true - -Style/SpaceAfterColon: - Enabled: true - -Style/SpaceAfterComma: - Enabled: true - -Style/SpaceAroundEqualsInParameterDefault: - Enabled: true - -Style/SpaceAroundKeyword: - Enabled: true - -Style/SpaceAroundOperators: - Enabled: true - -Style/SpaceBeforeFirstArg: - Enabled: true - # Defining a method with parameters needs parentheses. Style/MethodDefParentheses: Enabled: true -# Use `foo {}` not `foo{}`. -Style/SpaceBeforeBlockBraces: - Enabled: true - -# Use `foo { bar }` not `foo {bar}`. -Style/SpaceInsideBlockBraces: - Enabled: true - -# Use `{ a: 1 }` not `{a:1}`. -Style/SpaceInsideHashLiteralBraces: - Enabled: true - -Style/SpaceInsideParens: - Enabled: true - # Check quotes usage according to lint rule below. Style/StringLiterals: Enabled: true EnforcedStyle: double_quotes -# Detect hard tabs, no hard tabs. -Style/Tab: - Enabled: true - -# Blank lines should not have any spaces. -Style/TrailingBlankLines: - Enabled: true - -# No trailing whitespace. -Style/TrailingWhitespace: - Enabled: true - # Use quotes for string literals when they are enough. Style/UnneededPercentQ: Enabled: true diff --git a/Gemfile b/Gemfile index 953b85ccfe..7be644d80c 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,6 @@ gem "httparty" gem "aws-sdk", "~> 2", require: false gem "google-cloud-storage", "~> 1.3", require: false -gem 'mini_magick' +gem "mini_magick" gem "rubocop", require: false diff --git a/app/controllers/active_storage/variants_controller.rb b/app/controllers/active_storage/variants_controller.rb index dde7e1458f..d5e97e63fa 100644 --- a/app/controllers/active_storage/variants_controller.rb +++ b/app/controllers/active_storage/variants_controller.rb @@ -20,6 +20,6 @@ def processed_variant_for(blob_key) end def disposition_param - params[:disposition].presence_in(%w( inline attachment )) || 'inline' + params[:disposition].presence_in(%w( inline attachment )) || "inline" end end diff --git a/config/routes.rb b/config/routes.rb index d25f2c82f0..80e7f46184 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,5 +12,5 @@ route_for(:rails_blob_variation, encoded_blob_key, variation_key, filename) end - resolve('ActiveStorage::Variant') { |variant| route_for(:rails_variant, variant) } + resolve("ActiveStorage::Variant") { |variant| route_for(:rails_variant, variant) } end diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index b32ae34516..cf21a055be 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -29,7 +29,7 @@ class Engine < Rails::Engine # :nodoc: config.after_initialize do |app| ActiveStorage::VerifiedKeyWithExpiration.verifier = \ ActiveStorage::Variation.verifier = \ - Rails.application.message_verifier('ActiveStorage') + Rails.application.message_verifier("ActiveStorage") end end diff --git a/lib/active_storage/variation.rb b/lib/active_storage/variation.rb index abff288ac1..7656d73469 100644 --- a/lib/active_storage/variation.rb +++ b/lib/active_storage/variation.rb @@ -15,7 +15,7 @@ class << self def decode(key) new verifier.verify(key) end - + def encode(transformations) verifier.generate(transformations) end diff --git a/test/variant_test.rb b/test/variant_test.rb index 5294b87135..c7ff0d77e1 100644 --- a/test/variant_test.rb +++ b/test/variant_test.rb @@ -10,14 +10,14 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase test "resized variation" do variant = @blob.variant(resize: "100x100").processed - assert_match /racecar.jpg/, variant.url - assert_same_image "racecar-100x100.jpg", variant + assert_match /racecar.jpg/, variant.url + assert_same_image "racecar-100x100.jpg", variant end test "resized and monochrome variation" do variant = @blob.variant(resize: "100x100", monochrome: true).processed assert_match /racecar.jpg/, variant.url - assert_same_image "racecar-100x100-monochrome.jpg", variant + assert_same_image "racecar-100x100-monochrome.jpg", variant end end From d0407497ec83a8455d9ee85bb9cc34ef2449f0cb Mon Sep 17 00:00:00 2001 From: dixpac Date: Sat, 22 Jul 2017 15:41:32 +0200 Subject: [PATCH 214/289] Move all controller tests to controller/ dir --- test/{ => controllers}/direct_uploads_controller_test.rb | 0 test/{ => controllers}/disk_controller_test.rb | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/{ => controllers}/direct_uploads_controller_test.rb (100%) rename test/{ => controllers}/disk_controller_test.rb (100%) diff --git a/test/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb similarity index 100% rename from test/direct_uploads_controller_test.rb rename to test/controllers/direct_uploads_controller_test.rb diff --git a/test/disk_controller_test.rb b/test/controllers/disk_controller_test.rb similarity index 100% rename from test/disk_controller_test.rb rename to test/controllers/disk_controller_test.rb From 5fcaa197a77e70fbc7e7c267b5f012124f52ea5f Mon Sep 17 00:00:00 2001 From: Dino Maric Date: Sat, 22 Jul 2017 16:25:36 +0200 Subject: [PATCH 215/289] Assume Rails is defined --- lib/active_storage.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage.rb b/lib/active_storage.rb index 8b867f0145..164525653b 100644 --- a/lib/active_storage.rb +++ b/lib/active_storage.rb @@ -1,5 +1,5 @@ require "active_record" -require "active_storage/engine" if defined?(Rails) +require "active_storage/engine" module ActiveStorage extend ActiveSupport::Autoload From 470ba694035f77d41603d2e8c791449cb181b7d9 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 22 Jul 2017 09:38:16 -0500 Subject: [PATCH 216/289] Don't need to validate transformations actually Since they're only ever generated in signed form. Users never have direct access to dictate transformations. --- lib/active_storage/variation.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/active_storage/variation.rb b/lib/active_storage/variation.rb index 7656d73469..f7c81bb99a 100644 --- a/lib/active_storage/variation.rb +++ b/lib/active_storage/variation.rb @@ -4,11 +4,6 @@ class ActiveStorage::Variation class_attribute :verifier - ALLOWED_TRANSFORMATIONS = %i( - resize rotate format flip fill monochrome orient quality roll scale sharpen shave shear size thumbnail - transparent transpose transverse trim background bordercolor compress crop - ) - attr_reader :transformations class << self @@ -27,8 +22,6 @@ def initialize(transformations) def transform(image) transformations.each do |(method, argument)| - next unless eligible_transformation?(method) - if eligible_argument?(argument) image.public_send(method, argument) else @@ -42,11 +35,6 @@ def key end private - def eligible_transformation?(method) - method.to_sym.in?(ALLOWED_TRANSFORMATIONS) - end - - # FIXME: Consider whitelisting allowed arguments as well? def eligible_argument?(argument) argument.present? && argument != true end From d0a9174d55fe39f6c1dcbec9df684aeea691b21d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 22 Jul 2017 09:39:42 -0500 Subject: [PATCH 217/289] Move storage_services.yml to config for consistency --- {lib/active_storage => config}/storage_services.yml | 0 lib/tasks/activestorage.rake | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {lib/active_storage => config}/storage_services.yml (100%) diff --git a/lib/active_storage/storage_services.yml b/config/storage_services.yml similarity index 100% rename from lib/active_storage/storage_services.yml rename to config/storage_services.yml diff --git a/lib/tasks/activestorage.rake b/lib/tasks/activestorage.rake index ea83707224..2fba4eaa8d 100644 --- a/lib/tasks/activestorage.rake +++ b/lib/tasks/activestorage.rake @@ -7,7 +7,7 @@ namespace :activestorage do FileUtils.mkdir_p Rails.root.join("tmp/storage") puts "Made storage and tmp/storage directories for development and testing" - FileUtils.cp File.expand_path("../../active_storage/storage_services.yml", __FILE__), Rails.root.join("config") + FileUtils.cp File.expand_path("../../../config/storage_services.yml", __FILE__), Rails.root.join("config") puts "Copied default configuration to config/storage_services.yml" migration_file_path = "db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_active_storage_create_tables.rb" From 5b7c31c23a708de77b3d73b68aec0ba99c8be861 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 22 Jul 2017 09:40:53 -0500 Subject: [PATCH 218/289] Unused, we can extract from it out-of-repo --- lib/active_storage/download.rb | 90 ---------------------------------- 1 file changed, 90 deletions(-) delete mode 100644 lib/active_storage/download.rb diff --git a/lib/active_storage/download.rb b/lib/active_storage/download.rb deleted file mode 100644 index 6040a32de9..0000000000 --- a/lib/active_storage/download.rb +++ /dev/null @@ -1,90 +0,0 @@ -class ActiveStorage::Download - # Sending .ai files as application/postscript to Safari opens them in a blank, grey screen. - # Downloading .ai as application/postscript files in Safari appends .ps to the extension. - # Sending HTML, SVG, XML and SWF files as binary closes XSS vulnerabilities. - # Sending JS files as binary avoids InvalidCrossOriginRequest without compromising security. - CONTENT_TYPES_TO_RENDER_AS_BINARY = %w( - text/html - text/javascript - image/svg+xml - application/postscript - application/x-shockwave-flash - text/xml - application/xml - application/xhtml+xml - ) - - BINARY_CONTENT_TYPE = "application/octet-stream" - - def initialize(stored_file) - @stored_file = stored_file - end - - def headers(force_attachment: false) - { - x_accel_redirect: "/reproxy", - x_reproxy_url: reproxy_url, - content_type: content_type, - content_disposition: content_disposition(force_attachment), - x_frame_options: "SAMEORIGIN" - } - end - - private - def reproxy_url - @stored_file.depot_location.paths.first - end - - def content_type - if @stored_file.content_type.in? CONTENT_TYPES_TO_RENDER_AS_BINARY - BINARY_CONTENT_TYPE - else - @stored_file.content_type - end - end - - def content_disposition(force_attachment = false) - if force_attachment || content_type == BINARY_CONTENT_TYPE - "attachment; #{escaped_filename}" - else - "inline; #{escaped_filename}" - end - end - - # RFC2231 encoding for UTF-8 filenames, with an ASCII fallback - # first for unsupported browsers (IE < 9, perhaps others?). - # http://greenbytes.de/tech/tc2231/#encoding-2231-fb - def escaped_filename - filename = @stored_file.filename.sanitized - ascii_filename = encode_ascii_filename(filename) - utf8_filename = encode_utf8_filename(filename) - "#{ascii_filename}; #{utf8_filename}" - end - - TRADITIONAL_PARAMETER_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/ - - def encode_ascii_filename(filename) - # There is no reliable way to escape special or non-Latin characters - # in a traditionally quoted Content-Disposition filename parameter. - # Settle for transliterating to ASCII, then percent-escaping special - # characters, excluding spaces. - filename = I18n.transliterate(filename) - filename = percent_escape(filename, TRADITIONAL_PARAMETER_ESCAPED_CHAR) - %(filename="#{filename}") - end - - RFC5987_PARAMETER_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/ - - def encode_utf8_filename(filename) - # RFC2231 filename parameters can simply be percent-escaped according - # to RFC5987. - filename = percent_escape(filename, RFC5987_PARAMETER_ESCAPED_CHAR) - %(filename*=UTF-8''#{filename}) - end - - def percent_escape(string, pattern) - string.gsub(pattern) do |char| - char.bytes.map { |byte| "%%%02X" % byte }.join("") - end - end -end From d50679f4eefde1aca1ab71ba3c0109739cfdff3f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 22 Jul 2017 09:47:24 -0500 Subject: [PATCH 219/289] Move models and jobs to the app setup Follow engine conventions more closely --- {lib => app/jobs}/active_storage/purge_job.rb | 0 {lib => app/models}/active_storage/attachment.rb | 0 {lib => app/models}/active_storage/blob.rb | 0 {lib => app/models}/active_storage/filename.rb | 0 {lib => app/models}/active_storage/service.rb | 2 +- {lib => app/models}/active_storage/service/configurator.rb | 0 {lib => app/models}/active_storage/service/disk_service.rb | 0 {lib => app/models}/active_storage/service/gcs_service.rb | 0 {lib => app/models}/active_storage/service/mirror_service.rb | 0 {lib => app/models}/active_storage/service/s3_service.rb | 0 {lib => app/models}/active_storage/variant.rb | 0 {lib => app/models}/active_storage/variation.rb | 0 .../models}/active_storage/verified_key_with_expiration.rb | 0 test/test_helper.rb | 2 ++ 14 files changed, 3 insertions(+), 1 deletion(-) rename {lib => app/jobs}/active_storage/purge_job.rb (100%) rename {lib => app/models}/active_storage/attachment.rb (100%) rename {lib => app/models}/active_storage/blob.rb (100%) rename {lib => app/models}/active_storage/filename.rb (100%) rename {lib => app/models}/active_storage/service.rb (98%) rename {lib => app/models}/active_storage/service/configurator.rb (100%) rename {lib => app/models}/active_storage/service/disk_service.rb (100%) rename {lib => app/models}/active_storage/service/gcs_service.rb (100%) rename {lib => app/models}/active_storage/service/mirror_service.rb (100%) rename {lib => app/models}/active_storage/service/s3_service.rb (100%) rename {lib => app/models}/active_storage/variant.rb (100%) rename {lib => app/models}/active_storage/variation.rb (100%) rename {lib => app/models}/active_storage/verified_key_with_expiration.rb (100%) diff --git a/lib/active_storage/purge_job.rb b/app/jobs/active_storage/purge_job.rb similarity index 100% rename from lib/active_storage/purge_job.rb rename to app/jobs/active_storage/purge_job.rb diff --git a/lib/active_storage/attachment.rb b/app/models/active_storage/attachment.rb similarity index 100% rename from lib/active_storage/attachment.rb rename to app/models/active_storage/attachment.rb diff --git a/lib/active_storage/blob.rb b/app/models/active_storage/blob.rb similarity index 100% rename from lib/active_storage/blob.rb rename to app/models/active_storage/blob.rb diff --git a/lib/active_storage/filename.rb b/app/models/active_storage/filename.rb similarity index 100% rename from lib/active_storage/filename.rb rename to app/models/active_storage/filename.rb diff --git a/lib/active_storage/service.rb b/app/models/active_storage/service.rb similarity index 98% rename from lib/active_storage/service.rb rename to app/models/active_storage/service.rb index cba9cd9c83..745b1a615f 100644 --- a/lib/active_storage/service.rb +++ b/app/models/active_storage/service.rb @@ -1,4 +1,4 @@ -require_relative "log_subscriber" +require "active_storage/log_subscriber" # Abstract class serving as an interface for concrete services. # diff --git a/lib/active_storage/service/configurator.rb b/app/models/active_storage/service/configurator.rb similarity index 100% rename from lib/active_storage/service/configurator.rb rename to app/models/active_storage/service/configurator.rb diff --git a/lib/active_storage/service/disk_service.rb b/app/models/active_storage/service/disk_service.rb similarity index 100% rename from lib/active_storage/service/disk_service.rb rename to app/models/active_storage/service/disk_service.rb diff --git a/lib/active_storage/service/gcs_service.rb b/app/models/active_storage/service/gcs_service.rb similarity index 100% rename from lib/active_storage/service/gcs_service.rb rename to app/models/active_storage/service/gcs_service.rb diff --git a/lib/active_storage/service/mirror_service.rb b/app/models/active_storage/service/mirror_service.rb similarity index 100% rename from lib/active_storage/service/mirror_service.rb rename to app/models/active_storage/service/mirror_service.rb diff --git a/lib/active_storage/service/s3_service.rb b/app/models/active_storage/service/s3_service.rb similarity index 100% rename from lib/active_storage/service/s3_service.rb rename to app/models/active_storage/service/s3_service.rb diff --git a/lib/active_storage/variant.rb b/app/models/active_storage/variant.rb similarity index 100% rename from lib/active_storage/variant.rb rename to app/models/active_storage/variant.rb diff --git a/lib/active_storage/variation.rb b/app/models/active_storage/variation.rb similarity index 100% rename from lib/active_storage/variation.rb rename to app/models/active_storage/variation.rb diff --git a/lib/active_storage/verified_key_with_expiration.rb b/app/models/active_storage/verified_key_with_expiration.rb similarity index 100% rename from lib/active_storage/verified_key_with_expiration.rb rename to app/models/active_storage/verified_key_with_expiration.rb diff --git a/test/test_helper.rb b/test/test_helper.rb index 20b22049b3..5508061f6a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,6 @@ $LOAD_PATH << File.expand_path("../../app/controllers", __FILE__) +$LOAD_PATH << File.expand_path("../../app/models", __FILE__) +$LOAD_PATH << File.expand_path("../../app/jobs", __FILE__) require "bundler/setup" require "active_support" From e5f887236b60b207da85f44a5c2afee71db25c05 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 22 Jul 2017 09:51:39 -0500 Subject: [PATCH 220/289] Move model tests to models directory --- test/{ => models}/attachments_test.rb | 0 test/{ => models}/blob_test.rb | 0 test/{ => models}/variant_test.rb | 0 test/{ => models}/verified_key_with_expiration_test.rb | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename test/{ => models}/attachments_test.rb (100%) rename test/{ => models}/blob_test.rb (100%) rename test/{ => models}/variant_test.rb (100%) rename test/{ => models}/verified_key_with_expiration_test.rb (100%) diff --git a/test/attachments_test.rb b/test/models/attachments_test.rb similarity index 100% rename from test/attachments_test.rb rename to test/models/attachments_test.rb diff --git a/test/blob_test.rb b/test/models/blob_test.rb similarity index 100% rename from test/blob_test.rb rename to test/models/blob_test.rb diff --git a/test/variant_test.rb b/test/models/variant_test.rb similarity index 100% rename from test/variant_test.rb rename to test/models/variant_test.rb diff --git a/test/verified_key_with_expiration_test.rb b/test/models/verified_key_with_expiration_test.rb similarity index 100% rename from test/verified_key_with_expiration_test.rb rename to test/models/verified_key_with_expiration_test.rb From ca0b96d89141566866ac7289d5732cd70a1d68da Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 22 Jul 2017 09:54:23 -0500 Subject: [PATCH 221/289] Fix extension to run test automatically --- .../{variants_controller.rb => variants_controller_test.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/controllers/{variants_controller.rb => variants_controller_test.rb} (100%) diff --git a/test/controllers/variants_controller.rb b/test/controllers/variants_controller_test.rb similarity index 100% rename from test/controllers/variants_controller.rb rename to test/controllers/variants_controller_test.rb From e0b89fa4fc855d7c9253a1a46e8b272f3b3f5f8d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 22 Jul 2017 09:56:40 -0500 Subject: [PATCH 222/289] No need for explicit requires any more --- app/controllers/active_storage/disk_controller.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/controllers/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb index 16a295d00d..ae8cbddefa 100644 --- a/app/controllers/active_storage/disk_controller.rb +++ b/app/controllers/active_storage/disk_controller.rb @@ -1,9 +1,3 @@ -require "action_controller" -require "active_storage/blob" -require "active_storage/verified_key_with_expiration" - -require "active_support/core_ext/object/inclusion" - # This controller is a wrapper around local file downloading. It allows you to # make abstraction of the URL generation logic and to serve files with expiry # if you are using the +Disk+ service. From da12346695bd3e8079e0e10d2f6a6ccbc1515552 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 22 Jul 2017 10:00:16 -0500 Subject: [PATCH 223/289] Nix more needless requires --- app/jobs/active_storage/purge_job.rb | 2 -- test/models/attachments_test.rb | 5 ----- test/models/blob_test.rb | 1 - test/test_helper.rb | 4 ++++ 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/jobs/active_storage/purge_job.rb b/app/jobs/active_storage/purge_job.rb index b59d3687f8..87eb19815d 100644 --- a/app/jobs/active_storage/purge_job.rb +++ b/app/jobs/active_storage/purge_job.rb @@ -1,5 +1,3 @@ -require "active_job" - class ActiveStorage::PurgeJob < ActiveJob::Base # FIXME: Limit this to a custom ActiveStorage error retry_on StandardError diff --git a/test/models/attachments_test.rb b/test/models/attachments_test.rb index 9b88b18247..bfe631e5cb 100644 --- a/test/models/attachments_test.rb +++ b/test/models/attachments_test.rb @@ -1,10 +1,5 @@ require "test_helper" require "database/setup" -require "active_storage/blob" - -require "active_job" -ActiveJob::Base.queue_adapter = :test -ActiveJob::Base.logger = nil # ActiveRecord::Base.logger = Logger.new(STDOUT) diff --git a/test/models/blob_test.rb b/test/models/blob_test.rb index ddc000ed51..02fe653c33 100644 --- a/test/models/blob_test.rb +++ b/test/models/blob_test.rb @@ -1,6 +1,5 @@ require "test_helper" require "database/setup" -require "active_storage/blob" class ActiveStorage::BlobTest < ActiveSupport::TestCase test "create after upload sets byte size and checksum" do diff --git a/test/test_helper.rb b/test/test_helper.rb index 5508061f6a..a92c03cf7f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -8,6 +8,10 @@ require "active_support/testing/autorun" require "byebug" +require "active_job" +ActiveJob::Base.queue_adapter = :test +ActiveJob::Base.logger = nil + require "active_storage" require "active_storage/service" From 5ada4314c3272b1d6fc6bd15b2c0a9285c1227aa Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 22 Jul 2017 10:01:49 -0500 Subject: [PATCH 224/289] Even more needless requires --- test/controllers/direct_uploads_controller_test.rb | 3 --- test/controllers/disk_controller_test.rb | 1 - test/controllers/variants_controller_test.rb | 1 - test/models/variant_test.rb | 1 - 4 files changed, 6 deletions(-) diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index 8aa61f53cb..0083492929 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -1,9 +1,6 @@ require "test_helper" require "database/setup" -require "action_controller" -require "action_controller/test_case" - require "active_storage/direct_uploads_controller" if SERVICE_CONFIGURATIONS[:s3] diff --git a/test/controllers/disk_controller_test.rb b/test/controllers/disk_controller_test.rb index 834ad1bfd9..3e3f70de7a 100644 --- a/test/controllers/disk_controller_test.rb +++ b/test/controllers/disk_controller_test.rb @@ -2,7 +2,6 @@ require "database/setup" require "active_storage/disk_controller" -require "active_storage/verified_key_with_expiration" class ActiveStorage::DiskControllerTest < ActionController::TestCase setup do diff --git a/test/controllers/variants_controller_test.rb b/test/controllers/variants_controller_test.rb index 6753584d4d..d2b5c32df7 100644 --- a/test/controllers/variants_controller_test.rb +++ b/test/controllers/variants_controller_test.rb @@ -2,7 +2,6 @@ require "database/setup" require "active_storage/variants_controller" -require "active_storage/verified_key_with_expiration" class ActiveStorage::VariantsControllerTest < ActionController::TestCase setup do diff --git a/test/models/variant_test.rb b/test/models/variant_test.rb index c7ff0d77e1..276bf7e4fc 100644 --- a/test/models/variant_test.rb +++ b/test/models/variant_test.rb @@ -1,6 +1,5 @@ require "test_helper" require "database/setup" -require "active_storage/variant" class ActiveStorage::VariantTest < ActiveSupport::TestCase setup do From 9e81741b342a1a8a1ace94d356e023031d386689 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 10:56:53 -0500 Subject: [PATCH 225/289] Disk controller must rely on key alone Otherwise it can't be used to display variants. It's better anyway since all other services won't know about blobs either. Better simulation. Closes #71 --- app/controllers/active_storage/disk_controller.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/controllers/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb index ae8cbddefa..62380a3774 100644 --- a/app/controllers/active_storage/disk_controller.rb +++ b/app/controllers/active_storage/disk_controller.rb @@ -11,17 +11,18 @@ class ActiveStorage::DiskController < ActionController::Base def show if key = decode_verified_key - blob = ActiveStorage::Blob.find_by!(key: key) - - if stale?(etag: blob.checksum) - send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param - end + # FIXME: Find a way to set the correct content type + send_data disk_service.download(key), filename: params[:filename], disposition: disposition_param else head :not_found end end private + def disk_service + ActiveStorage::Blob.service + end + def decode_verified_key ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key]) end From 8f20624820ed0922b33fceb4013d3ff11015b366 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 11:03:25 -0500 Subject: [PATCH 226/289] Switch to a single message verifier No need for this proliferation --- app/models/active_storage/variation.rb | 6 ++---- .../active_storage/verified_key_with_expiration.rb | 6 ++---- lib/active_storage.rb | 2 ++ lib/active_storage/engine.rb | 9 ++------- test/test_helper.rb | 1 + 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/app/models/active_storage/variation.rb b/app/models/active_storage/variation.rb index f7c81bb99a..b37397fcad 100644 --- a/app/models/active_storage/variation.rb +++ b/app/models/active_storage/variation.rb @@ -2,17 +2,15 @@ # A set of transformations that can be applied to a blob to create a variant. class ActiveStorage::Variation - class_attribute :verifier - attr_reader :transformations class << self def decode(key) - new verifier.verify(key) + new ActiveStorage.verifier.verify(key) end def encode(transformations) - verifier.generate(transformations) + ActiveStorage.verifier.generate(transformations) end end diff --git a/app/models/active_storage/verified_key_with_expiration.rb b/app/models/active_storage/verified_key_with_expiration.rb index 4a46483db5..5cb07c6988 100644 --- a/app/models/active_storage/verified_key_with_expiration.rb +++ b/app/models/active_storage/verified_key_with_expiration.rb @@ -1,13 +1,11 @@ class ActiveStorage::VerifiedKeyWithExpiration - class_attribute :verifier - class << self def encode(key, expires_in: nil) - verifier.generate([ key, expires_at(expires_in) ]) + ActiveStorage.verifier.generate([ key, expires_at(expires_in) ]) end def decode(encoded_key) - key, expires_at = verifier.verified(encoded_key) + key, expires_at = ActiveStorage.verifier.verified(encoded_key) key if key && fresh?(expires_at) end diff --git a/lib/active_storage.rb b/lib/active_storage.rb index 164525653b..4032fd59a7 100644 --- a/lib/active_storage.rb +++ b/lib/active_storage.rb @@ -6,4 +6,6 @@ module ActiveStorage autoload :Blob autoload :Service + + mattr_accessor :verifier end diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index cf21a055be..95ed021ce0 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -22,14 +22,9 @@ class Engine < Rails::Engine # :nodoc: end end - initializer "active_storage.verifiers" do - require "active_storage/verified_key_with_expiration" - require "active_storage/variation" - + initializer "active_storage.verifier" do config.after_initialize do |app| - ActiveStorage::VerifiedKeyWithExpiration.verifier = \ - ActiveStorage::Variation.verifier = \ - Rails.application.message_verifier("ActiveStorage") + ActiveStorage.verifier = Rails.application.message_verifier("ActiveStorage") end end diff --git a/test/test_helper.rb b/test/test_helper.rb index a92c03cf7f..1d9737c4a4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -33,6 +33,7 @@ require "active_storage/variation" ActiveStorage::Variation.verifier = ActiveSupport::MessageVerifier.new("Testing") +ActiveStorage.verifier = ActiveSupport::MessageVerifier.new("Testing") class ActiveSupport::TestCase private From 46da4ee7daf1ecaa2fc47a260ccb58e119a1b5ea Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 11:05:20 -0500 Subject: [PATCH 227/289] Switch to simpler signed_id for blob rather than full GlobalID We don't need to lookup multiple different classes, so no need to use a globalid. --- .../active_storage/direct_uploads_controller.rb | 5 ++++- .../active_storage/variants_controller.rb | 17 ++++++++--------- app/models/active_storage/blob.rb | 9 +++++++++ .../active_storage/service/disk_service.rb | 1 + config/routes.rb | 17 ++++++++++------- lib/active_storage/attached.rb | 2 +- .../direct_uploads_controller_test.rb | 8 ++++---- test/controllers/variants_controller_test.rb | 2 +- test/models/attachments_test.rb | 2 +- test/models/blob_test.rb | 1 + .../models/verified_key_with_expiration_test.rb | 1 + test/test_helper.rb | 6 ------ 12 files changed, 41 insertions(+), 30 deletions(-) diff --git a/app/controllers/active_storage/direct_uploads_controller.rb b/app/controllers/active_storage/direct_uploads_controller.rb index dccd864e8d..0d1b806f9f 100644 --- a/app/controllers/active_storage/direct_uploads_controller.rb +++ b/app/controllers/active_storage/direct_uploads_controller.rb @@ -1,7 +1,10 @@ +# Creates a new blob on the server side in anticipation of a direct-to-service upload from the client side. +# When the client-side upload is completed, the signed_blob_id can be submitted as part of the form to reference +# the blob that was created up front. class ActiveStorage::DirectUploadsController < ActionController::Base def create blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args) - render json: { url: blob.url_for_direct_upload, sgid: blob.to_sgid.to_param } + render json: { upload_to_url: blob.url_for_direct_upload, signed_blob_id: blob.signed_id } end private diff --git a/app/controllers/active_storage/variants_controller.rb b/app/controllers/active_storage/variants_controller.rb index d5e97e63fa..a65d7d7571 100644 --- a/app/controllers/active_storage/variants_controller.rb +++ b/app/controllers/active_storage/variants_controller.rb @@ -1,22 +1,21 @@ +require "active_storage/variant" + class ActiveStorage::VariantsController < ActionController::Base def show - if blob_key = decode_verified_blob_key - redirect_to processed_variant_for(blob_key).url(disposition: disposition_param) + if blob = find_signed_blob + redirect_to ActiveStorage::Variant.new(blob, decoded_variation).processed.url(disposition: disposition_param) else head :not_found end end private - def decode_verified_blob_key - ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_blob_key]) + def find_signed_blob + ActiveStorage::Blob.find_signed(params[:signed_blob_id]) end - def processed_variant_for(blob_key) - ActiveStorage::Variant.new( - ActiveStorage::Blob.find_by!(key: blob_key), - ActiveStorage::Variation.decode(params[:variation_key]) - ).processed + def decoded_variation + ActiveStorage::Variation.decode(params[:variation_key]) end def disposition_param diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index 6bd3941cd8..7b45d3ad25 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -2,6 +2,7 @@ require "active_storage/filename" require "active_storage/purge_job" require "active_storage/variant" +require "active_storage/variation" # Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at class ActiveStorage::Blob < ActiveRecord::Base @@ -13,6 +14,10 @@ class ActiveStorage::Blob < ActiveRecord::Base class_attribute :service class << self + def find_signed(id) + find ActiveStorage.verifier.verify(id) + end + def build_after_upload(io:, filename:, content_type: nil, metadata: nil) new.tap do |blob| blob.filename = filename @@ -33,6 +38,10 @@ def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: end + def signed_id + ActiveStorage.verifier.generate(id) + end + def key # We can't wait until the record is first saved to have a key for it self[:key] ||= self.class.generate_unique_secure_token diff --git a/app/models/active_storage/service/disk_service.rb b/app/models/active_storage/service/disk_service.rb index a2a27528c1..905f41c138 100644 --- a/app/models/active_storage/service/disk_service.rb +++ b/app/models/active_storage/service/disk_service.rb @@ -2,6 +2,7 @@ require "pathname" require "digest/md5" require "active_support/core_ext/numeric/bytes" +require "active_storage/verified_key_with_expiration" class ActiveStorage::Service::DiskService < ActiveStorage::Service attr_reader :root diff --git a/config/routes.rb b/config/routes.rb index 80e7f46184..78fa0e707b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,16 +1,19 @@ Rails.application.routes.draw do - get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob - post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads - get "/rails/active_storage/variants/:encoded_blob_key/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation + + get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation direct :rails_variant do |variant| - encoded_blob_key = ActiveStorage::VerifiedKeyWithExpiration.encode(variant.blob.key) - variation_key = variant.variation.key - filename = variant.blob.filename + signed_blob_id = variant.blob.signed_id + variation_key = variant.variation.key + filename = variant.blob.filename - route_for(:rails_blob_variation, encoded_blob_key, variation_key, filename) + route_for(:rails_blob_variation, signed_blob_id, variation_key, filename) end resolve("ActiveStorage::Variant") { |variant| route_for(:rails_variant, variant) } + + + get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob + post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads end diff --git a/lib/active_storage/attached.rb b/lib/active_storage/attached.rb index d5ded51e2b..9fa7b8e021 100644 --- a/lib/active_storage/attached.rb +++ b/lib/active_storage/attached.rb @@ -26,7 +26,7 @@ def create_blob_from(attachable) when Hash ActiveStorage::Blob.create_after_upload!(attachable) when String - GlobalID::Locator.locate_signed(attachable) + ActiveStorage::Blob.find_signed(attachable) else nil end diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index 0083492929..06e76cfa8b 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -24,8 +24,8 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase details = JSON.parse(@response.body) - assert_match /#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["url"] - assert_equal "hello.txt", GlobalID::Locator.locate_signed(details["sgid"]).filename.to_s + assert_match /#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["upload_to_url"] + assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s end end else @@ -54,8 +54,8 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase details = JSON.parse(@response.body) - assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["url"] - assert_equal "hello.txt", GlobalID::Locator.locate_signed(details["sgid"]).filename.to_s + assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["upload_to_url"] + assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s end end else diff --git a/test/controllers/variants_controller_test.rb b/test/controllers/variants_controller_test.rb index d2b5c32df7..8d437bedbb 100644 --- a/test/controllers/variants_controller_test.rb +++ b/test/controllers/variants_controller_test.rb @@ -14,7 +14,7 @@ class ActiveStorage::VariantsControllerTest < ActionController::TestCase test "showing variant inline" do get :show, params: { filename: @blob.filename, - encoded_blob_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), + signed_blob_id: @blob.signed_id, variation_key: ActiveStorage::Variation.encode(resize: "100x100") } assert_redirected_to /racecar.jpg\?disposition=inline/ diff --git a/test/models/attachments_test.rb b/test/models/attachments_test.rb index bfe631e5cb..c0f5db819d 100644 --- a/test/models/attachments_test.rb +++ b/test/models/attachments_test.rb @@ -21,7 +21,7 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase end test "attach existing sgid blob" do - @user.avatar.attach create_blob(filename: "funky.jpg").to_sgid.to_s + @user.avatar.attach create_blob(filename: "funky.jpg").signed_id assert_equal "funky.jpg", @user.avatar.filename.to_s end diff --git a/test/models/blob_test.rb b/test/models/blob_test.rb index 02fe653c33..ded11e5dbe 100644 --- a/test/models/blob_test.rb +++ b/test/models/blob_test.rb @@ -1,5 +1,6 @@ require "test_helper" require "database/setup" +require "active_storage/verified_key_with_expiration" class ActiveStorage::BlobTest < ActiveSupport::TestCase test "create after upload sets byte size and checksum" do diff --git a/test/models/verified_key_with_expiration_test.rb b/test/models/verified_key_with_expiration_test.rb index ee4dc7e02e..dd69e7cb10 100644 --- a/test/models/verified_key_with_expiration_test.rb +++ b/test/models/verified_key_with_expiration_test.rb @@ -1,5 +1,6 @@ require "test_helper" require "active_support/core_ext/securerandom" +require "active_storage/verified_key_with_expiration" class ActiveStorage::VerifiedKeyWithExpirationTest < ActiveSupport::TestCase FIXTURE_KEY = SecureRandom.base58(24) diff --git a/test/test_helper.rb b/test/test_helper.rb index 1d9737c4a4..650e997205 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -28,11 +28,6 @@ ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: Dir.mktmpdir("active_storage_tests")) ActiveStorage::Service.logger = ActiveSupport::Logger.new(STDOUT) -require "active_storage/verified_key_with_expiration" -ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") - -require "active_storage/variation" -ActiveStorage::Variation.verifier = ActiveSupport::MessageVerifier.new("Testing") ActiveStorage.verifier = ActiveSupport::MessageVerifier.new("Testing") class ActiveSupport::TestCase @@ -71,4 +66,3 @@ class ActionController::TestCase require "global_id" GlobalID.app = "ActiveStorageExampleApp" ActiveRecord::Base.send :include, GlobalID::Identification -SignedGlobalID.verifier = ActiveStorage::VerifiedKeyWithExpiration.verifier From c285c6824dc186e00040b7283877fea917050275 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 11:06:06 -0500 Subject: [PATCH 228/289] Provide a BlobsController for stable blob URLs We need to have stable urls for blobs and variants or caching won't work. So provide a controller that can give that and redirect to the service URL upon lookup. --- .../active_storage/blobs_controller.rb | 22 +++++++++++++++++++ config/routes.rb | 8 +++++++ 2 files changed, 30 insertions(+) create mode 100644 app/controllers/active_storage/blobs_controller.rb diff --git a/app/controllers/active_storage/blobs_controller.rb b/app/controllers/active_storage/blobs_controller.rb new file mode 100644 index 0000000000..5a527d0a33 --- /dev/null +++ b/app/controllers/active_storage/blobs_controller.rb @@ -0,0 +1,22 @@ +# Take a signed permanent reference for a blob and turn it into an expiring service URL for its download. +# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the +# security-through-obscurity factor of the signed blob references, you'll need to implement your own +# authenticated redirection controller. +class ActiveStorage::BlobsController < ActionController::Base + def show + if blob = find_signed_blob + redirect_to blob.url(disposition: disposition_param) + else + head :not_found + end + end + + private + def find_signed_blob + ActiveStorage::Blob.find_signed(params[:signed_id]) + end + + def disposition_param + params[:disposition].presence_in(%w( inline attachment )) || "inline" + end +end diff --git a/config/routes.rb b/config/routes.rb index 78fa0e707b..b368e35cac 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,12 @@ Rails.application.routes.draw do + get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob + + direct :rails_blob do |blob| + route_for(:rails_service_blob, blob.signed_id, blob.filename) + end + + resolve("ActiveStorage::Blob") { |blob| route_for(:rails_blob, blob) } + resolve("ActiveStorage::Attachment") { |attachment| route_for(:rails_blob, attachment.blob) } get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation From 347dc166324c108b4a9c25c5ab03222a2f42b1d0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 13:19:32 -0500 Subject: [PATCH 229/289] VerifiedKeyWithExpiration no longer needed Thanks to rails/rails#29854! This does mean that we now depend on rails/rails master. --- Gemfile | 5 +---- Gemfile.lock | 13 +++++------ README.md | 1 - .../active_storage/disk_controller.rb | 2 +- .../active_storage/service/disk_service.rb | 3 +-- .../verified_key_with_expiration.rb | 22 ------------------- test/controllers/disk_controller_test.rb | 4 ++-- test/models/blob_test.rb | 3 +-- .../verified_key_with_expiration_test.rb | 20 ----------------- 9 files changed, 11 insertions(+), 62 deletions(-) delete mode 100644 app/models/active_storage/verified_key_with_expiration.rb delete mode 100644 test/models/verified_key_with_expiration_test.rb diff --git a/Gemfile b/Gemfile index 7be644d80c..55b0deec27 100644 --- a/Gemfile +++ b/Gemfile @@ -4,10 +4,7 @@ git_source(:github) { |repo_path| "https://github.com/#{repo_path}.git" } gemspec -gem "activesupport", github: "rails/rails" -gem "activerecord", github: "rails/rails" -gem "actionpack", github: "rails/rails" -gem "activejob", github: "rails/rails" +gem "rails", github: "rails/rails" gem "rake" gem "byebug" diff --git a/Gemfile.lock b/Gemfile.lock index e18acab95b..4319b1e22d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/rails/rails.git - revision: 5c16dd35a23f75038baf1527143ee44accf081ff + revision: 127b475dc251a06942fe0cd2de2e0545cf5ed69f specs: actioncable (5.2.0.alpha) actionpack (= 5.2.0.alpha) @@ -126,7 +126,7 @@ GEM multi_json (~> 1.10) loofah (2.0.3) nokogiri (>= 1.5.9) - mail (2.6.5) + mail (2.6.6) mime-types (>= 1.16, < 4) memoist (0.16.0) method_source (0.8.2) @@ -135,7 +135,7 @@ GEM mime-types-data (3.2016.0521) mini_magick (4.8.0) mini_portile2 (2.2.0) - minitest (5.10.2) + minitest (5.10.3) multi_json (1.12.1) multi_xml (0.6.0) multipart-post (2.0.0) @@ -199,20 +199,17 @@ PLATFORMS ruby DEPENDENCIES - actionpack! - activejob! - activerecord! activestorage! - activesupport! aws-sdk (~> 2) bundler (~> 1.15) byebug google-cloud-storage (~> 1.3) httparty mini_magick + rails! rake rubocop sqlite3 BUNDLED WITH - 1.15.2 + 1.15.3 diff --git a/README.md b/README.md index b56999cae7..901d8f4f23 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,6 @@ Variation of image attachment: - Read metadata via Marcel? - Add Migrator to copy/move between services - [Explore direct uploads to cloud](https://github.com/rails/activestorage/pull/19) -- Extract VerifiedKeyWithExpiration into Rails as a feature of MessageVerifier ## Roadmap diff --git a/app/controllers/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb index 62380a3774..7269239216 100644 --- a/app/controllers/active_storage/disk_controller.rb +++ b/app/controllers/active_storage/disk_controller.rb @@ -24,7 +24,7 @@ def disk_service end def decode_verified_key - ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key]) + ActiveStorage.verifier.verified(params[:encoded_key]) end def disposition_param diff --git a/app/models/active_storage/service/disk_service.rb b/app/models/active_storage/service/disk_service.rb index 905f41c138..c7c45e2146 100644 --- a/app/models/active_storage/service/disk_service.rb +++ b/app/models/active_storage/service/disk_service.rb @@ -2,7 +2,6 @@ require "pathname" require "digest/md5" require "active_support/core_ext/numeric/bytes" -require "active_storage/verified_key_with_expiration" class ActiveStorage::Service::DiskService < ActiveStorage::Service attr_reader :root @@ -54,7 +53,7 @@ def exist?(key) def url(key, expires_in:, disposition:, filename:) instrument :url, key do |payload| - verified_key_with_expiration = ActiveStorage::VerifiedKeyWithExpiration.encode(key, expires_in: expires_in) + verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in) generated_url = if defined?(Rails) && defined?(Rails.application) diff --git a/app/models/active_storage/verified_key_with_expiration.rb b/app/models/active_storage/verified_key_with_expiration.rb deleted file mode 100644 index 5cb07c6988..0000000000 --- a/app/models/active_storage/verified_key_with_expiration.rb +++ /dev/null @@ -1,22 +0,0 @@ -class ActiveStorage::VerifiedKeyWithExpiration - class << self - def encode(key, expires_in: nil) - ActiveStorage.verifier.generate([ key, expires_at(expires_in) ]) - end - - def decode(encoded_key) - key, expires_at = ActiveStorage.verifier.verified(encoded_key) - - key if key && fresh?(expires_at) - end - - private - def expires_at(expires_in) - expires_in ? Time.now.utc.advance(seconds: expires_in) : nil - end - - def fresh?(expires_at) - expires_at.nil? || Time.now.utc < expires_at - end - end -end diff --git a/test/controllers/disk_controller_test.rb b/test/controllers/disk_controller_test.rb index 3e3f70de7a..c427942c57 100644 --- a/test/controllers/disk_controller_test.rb +++ b/test/controllers/disk_controller_test.rb @@ -11,13 +11,13 @@ class ActiveStorage::DiskControllerTest < ActionController::TestCase end test "showing blob inline" do - get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes) } + get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage.verifier.generate(@blob.key, expires_in: 5.minutes) } assert_equal "inline; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end test "sending blob as attachment" do - get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), disposition: :attachment } + get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage.verifier.generate(@blob.key, expires_in: 5.minutes), disposition: :attachment } assert_equal "attachment; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end diff --git a/test/models/blob_test.rb b/test/models/blob_test.rb index ded11e5dbe..45c8b7168f 100644 --- a/test/models/blob_test.rb +++ b/test/models/blob_test.rb @@ -1,6 +1,5 @@ require "test_helper" require "database/setup" -require "active_storage/verified_key_with_expiration" class ActiveStorage::BlobTest < ActiveSupport::TestCase test "create after upload sets byte size and checksum" do @@ -36,6 +35,6 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase private def expected_url_for(blob, disposition: :inline) - "/rails/active_storage/disk/#{ActiveStorage::VerifiedKeyWithExpiration.encode(blob.key, expires_in: 5.minutes)}/#{blob.filename}?disposition=#{disposition}" + "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes)}/#{blob.filename}?disposition=#{disposition}" end end diff --git a/test/models/verified_key_with_expiration_test.rb b/test/models/verified_key_with_expiration_test.rb deleted file mode 100644 index dd69e7cb10..0000000000 --- a/test/models/verified_key_with_expiration_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require "test_helper" -require "active_support/core_ext/securerandom" -require "active_storage/verified_key_with_expiration" - -class ActiveStorage::VerifiedKeyWithExpirationTest < ActiveSupport::TestCase - FIXTURE_KEY = SecureRandom.base58(24) - - test "without expiration" do - encoded_key = ActiveStorage::VerifiedKeyWithExpiration.encode(FIXTURE_KEY) - assert_equal FIXTURE_KEY, ActiveStorage::VerifiedKeyWithExpiration.decode(encoded_key) - end - - test "with expiration" do - encoded_key = ActiveStorage::VerifiedKeyWithExpiration.encode(FIXTURE_KEY, expires_in: 1.minute) - assert_equal FIXTURE_KEY, ActiveStorage::VerifiedKeyWithExpiration.decode(encoded_key) - - travel 2.minutes - assert_nil ActiveStorage::VerifiedKeyWithExpiration.decode(encoded_key) - end -end From 5889560427e56cb40861c0d57582857386f7e8fd Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 13:26:52 -0500 Subject: [PATCH 230/289] Update the README with more explicit expectation setting --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 901d8f4f23..9e4749b1c6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ Active Storage makes it simple to upload and reference files in cloud services, and attach those files to Active Records. It also provides a disk service for testing or local deployments, but the focus is on cloud storage. +## Compatibility & Expectations + +Active Storage only works with the development version of Rails 5.2+ (as of July 19, 2017). This separate repository is a staging ground for the upcoming inclusion in rails/rails prior to the Rails 5.2 release. It is not intended to be a long-term stand-alone repository. + +Furthermore, this repository is likely to be in heavy flux prior to the merge to rails/rails. You're heartedly encouraged to follow along and even use Active Storage in this phase, but don't be surprised if the API suffers frequent breaking changes prior to the merge. + ## Compared to other storage solutions A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in [Blob](https://github.com/rails/activestorage/blob/master/lib/active_storage/blob.rb) and [Attachment](https://github.com/rails/activestorage/blob/master/lib/active_storage/attachment.rb) models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses GlobalID to provide polymorphic associations via the join model of `Attachment`, which then connects to the actual `Blob`. @@ -82,15 +88,9 @@ Variation of image attachment: ## Todos - Document all the classes -- Strip Download of its responsibilities and delete class - Convert MirrorService to use threading - Read metadata via Marcel? - Add Migrator to copy/move between services -- [Explore direct uploads to cloud](https://github.com/rails/activestorage/pull/19) - -## Roadmap - -This separate repository is a staging ground for eventual inclusion in rails/rails prior to the Rails 5.2 release. It is not intended to be a long-term stand-alone repository. Compatibility with prior versions of Rails is not a development priority either. ## License From 4efbeaeaab72be070e203f39726b37703c1db1fa Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Sun, 23 Jul 2017 21:15:30 +0200 Subject: [PATCH 231/289] Use parsed_body to auto parse the response as JSON. --- .../direct_uploads_controller_test.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index 06e76cfa8b..60b15b1fdd 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -22,10 +22,10 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase post :create, params: { blob: { filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } - details = JSON.parse(@response.body) - - assert_match /#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["upload_to_url"] - assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s + @response.parsed_body.tap do |details| + assert_match(/#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["upload_to_url"]) + assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s + end end end else @@ -52,10 +52,10 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase post :create, params: { blob: { filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } - details = JSON.parse(@response.body) - - assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["upload_to_url"] - assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s + @response.parsed_body.tap do |details| + assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["upload_to_url"] + assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s + end end end else From 5963766d840ddcdb577a1bd10eb1491a4ef9132f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 9 Jul 2017 18:48:26 +0200 Subject: [PATCH 232/289] Explore regular polymorphic associations rather than record_gid --- app/models/active_storage/attachment.rb | 10 +--------- lib/active_storage/attached/macros.rb | 6 ++++++ lib/active_storage/attached/many.rb | 4 ++-- lib/active_storage/attached/one.rb | 4 ++-- lib/active_storage/migration.rb | 9 +++++---- test/models/attachments_test.rb | 8 ++++++++ 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/app/models/active_storage/attachment.rb b/app/models/active_storage/attachment.rb index 20c619aa5a..1dd202ca45 100644 --- a/app/models/active_storage/attachment.rb +++ b/app/models/active_storage/attachment.rb @@ -6,19 +6,11 @@ class ActiveStorage::Attachment < ActiveRecord::Base self.table_name = "active_storage_attachments" + belongs_to :record, polymorphic: true belongs_to :blob, class_name: "ActiveStorage::Blob" delegate_missing_to :blob - def record - @record ||= GlobalID::Locator.locate(record_gid) - end - - def record=(record) - @record = record - self.record_gid = record&.to_gid - end - def purge blob.purge destroy diff --git a/lib/active_storage/attached/macros.rb b/lib/active_storage/attached/macros.rb index 1e0f9a6b7e..0452089a5f 100644 --- a/lib/active_storage/attached/macros.rb +++ b/lib/active_storage/attached/macros.rb @@ -16,6 +16,9 @@ def has_one_attached(name, dependent: :purge_later) instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::One.new(name, self)) end + has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record + has_one :"#{name}_blob", through: :"#{name}_attachment" + if dependent == :purge_later before_destroy { public_send(name).purge_later } end @@ -38,6 +41,9 @@ def has_many_attached(name, dependent: :purge_later) instance_variable_set("@active_storage_attached_#{name}", ActiveStorage::Attached::Many.new(name, self)) end + has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment" + has_many :"#{name}_blobs", through: :"#{name}_attachments" + if dependent == :purge_later before_destroy { public_send(name).purge_later } end diff --git a/lib/active_storage/attached/many.rb b/lib/active_storage/attached/many.rb index 99d980196a..129f166776 100644 --- a/lib/active_storage/attached/many.rb +++ b/lib/active_storage/attached/many.rb @@ -7,14 +7,14 @@ class ActiveStorage::Attached::Many < ActiveStorage::Attached # You don't have to call this method to access the attachments' methods as # they are all available at the model level. def attachments - @attachments ||= ActiveStorage::Attachment.where(record_gid: record.to_gid.to_s, name: name) + @attachments ||= record.public_send("#{name}_attachments") end # Associates one or several attachments with the current record, saving # them to the database. def attach(*attachables) @attachments = attachments | Array(attachables).flatten.collect do |attachable| - ActiveStorage::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) + ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) end end diff --git a/lib/active_storage/attached/one.rb b/lib/active_storage/attached/one.rb index 80e4cb6234..02fc9c9abc 100644 --- a/lib/active_storage/attached/one.rb +++ b/lib/active_storage/attached/one.rb @@ -7,13 +7,13 @@ class ActiveStorage::Attached::One < ActiveStorage::Attached # You don't have to call this method to access the attachment's methods as # they are all available at the model level. def attachment - @attachment ||= ActiveStorage::Attachment.find_by(record_gid: record.to_gid.to_s, name: name) + @attachment ||= record.public_send("#{name}_attachment") end # Associates a given attachment with the current record, saving it to the # database. def attach(attachable) - @attachment = ActiveStorage::Attachment.create!(record_gid: record.to_gid.to_s, name: name, blob: create_blob_from(attachable)) + @attachment = ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) end # Checks the presence of the attachment. diff --git a/lib/active_storage/migration.rb b/lib/active_storage/migration.rb index c56e7a1786..99d8b8554b 100644 --- a/lib/active_storage/migration.rb +++ b/lib/active_storage/migration.rb @@ -14,15 +14,16 @@ def change create_table :active_storage_attachments do |t| t.string :name - t.string :record_gid + t.string :record_type + t.integer :record_id t.integer :blob_id t.datetime :created_at - t.index :record_gid t.index :blob_id - t.index [ :record_gid, :name ] - t.index [ :record_gid, :blob_id ], unique: true + t.index [ :record_type, :record_id ] + t.index [ :record_type, :record_id, :name ], name: "index_active_storage_attachments_record_and_name" + t.index [ :record_type, :record_id, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true end end end diff --git a/test/models/attachments_test.rb b/test/models/attachments_test.rb index c0f5db819d..1a9fc6f932 100644 --- a/test/models/attachments_test.rb +++ b/test/models/attachments_test.rb @@ -68,6 +68,14 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase assert_equal "country.jpg", @user.highlights.second.filename.to_s end + test "find attached blobs" do + @user.highlights.attach( + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) + + User.where(id: @user.id).includes(highlights_attachments: :blob).first + end + test "purge attached blobs" do @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") highlight_keys = @user.highlights.collect(&:key) From 212f925654f944067f18429ca02d902473214722 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Sun, 23 Jul 2017 21:19:34 +0200 Subject: [PATCH 233/289] Collapse indeces per Jeremy. --- lib/active_storage/migration.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/active_storage/migration.rb b/lib/active_storage/migration.rb index 99d8b8554b..e843c1b630 100644 --- a/lib/active_storage/migration.rb +++ b/lib/active_storage/migration.rb @@ -21,9 +21,7 @@ def change t.datetime :created_at t.index :blob_id - t.index [ :record_type, :record_id ] - t.index [ :record_type, :record_id, :name ], name: "index_active_storage_attachments_record_and_name" - t.index [ :record_type, :record_id, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true end end end From a4f36f957e013f6da34e04f0d3f1d86d86491454 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Sun, 23 Jul 2017 21:41:16 +0200 Subject: [PATCH 234/289] Fix attaching with standard Rails associations. Removes needless ivar caching (a Rails association handles that). Inserts a reload and a nil assign, since the association proxy doesn't seem to that it's been destroyed through `purge`. --- lib/active_storage/attached/many.rb | 9 ++++----- lib/active_storage/attached/one.rb | 13 +++++++++---- test/models/attachments_test.rb | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/active_storage/attached/many.rb b/lib/active_storage/attached/many.rb index 129f166776..704369ba89 100644 --- a/lib/active_storage/attached/many.rb +++ b/lib/active_storage/attached/many.rb @@ -7,15 +7,15 @@ class ActiveStorage::Attached::Many < ActiveStorage::Attached # You don't have to call this method to access the attachments' methods as # they are all available at the model level. def attachments - @attachments ||= record.public_send("#{name}_attachments") + record.public_send("#{name}_attachments") end # Associates one or several attachments with the current record, saving # them to the database. def attach(*attachables) - @attachments = attachments | Array(attachables).flatten.collect do |attachable| + record.public_send("#{name}_attachments=", attachments | Array(attachables).flat_map do |attachable| ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) - end + end) end # Checks the presence of attachments. @@ -34,7 +34,7 @@ def attached? def purge if attached? attachments.each(&:purge) - @attachments = nil + attachments.reload end end @@ -42,7 +42,6 @@ def purge def purge_later if attached? attachments.each(&:purge_later) - @attachments = nil end end end diff --git a/lib/active_storage/attached/one.rb b/lib/active_storage/attached/one.rb index 02fc9c9abc..d255412842 100644 --- a/lib/active_storage/attached/one.rb +++ b/lib/active_storage/attached/one.rb @@ -7,13 +7,14 @@ class ActiveStorage::Attached::One < ActiveStorage::Attached # You don't have to call this method to access the attachment's methods as # they are all available at the model level. def attachment - @attachment ||= record.public_send("#{name}_attachment") + record.public_send("#{name}_attachment") end # Associates a given attachment with the current record, saving it to the # database. def attach(attachable) - @attachment = ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) + write_attachment \ + ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) end # Checks the presence of the attachment. @@ -32,7 +33,7 @@ def attached? def purge if attached? attachment.purge - @attachment = nil + write_attachment nil end end @@ -40,7 +41,11 @@ def purge def purge_later if attached? attachment.purge_later - @attachment = nil end end + + private + def write_attachment(attachment) + record.public_send("#{name}_attachment=", attachment) + end end diff --git a/test/models/attachments_test.rb b/test/models/attachments_test.rb index 1a9fc6f932..45f62b0bbf 100644 --- a/test/models/attachments_test.rb +++ b/test/models/attachments_test.rb @@ -70,9 +70,9 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase test "find attached blobs" do @user.highlights.attach( - { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) - + User.where(id: @user.id).includes(highlights_attachments: :blob).first end From 15efa6720f9dc6efe27c717d9e32b31b2d45b7b8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 15:51:01 -0500 Subject: [PATCH 235/289] Specify verification purposes --- app/controllers/active_storage/disk_controller.rb | 2 +- app/models/active_storage/blob.rb | 4 ++-- app/models/active_storage/service/disk_service.rb | 2 +- app/models/active_storage/variation.rb | 4 ++-- test/controllers/disk_controller_test.rb | 4 ++-- test/models/blob_test.rb | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/controllers/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb index 7269239216..a42b4833a7 100644 --- a/app/controllers/active_storage/disk_controller.rb +++ b/app/controllers/active_storage/disk_controller.rb @@ -24,7 +24,7 @@ def disk_service end def decode_verified_key - ActiveStorage.verifier.verified(params[:encoded_key]) + ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key) end def disposition_param diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index 7b45d3ad25..fdf9a2c37d 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -15,7 +15,7 @@ class ActiveStorage::Blob < ActiveRecord::Base class << self def find_signed(id) - find ActiveStorage.verifier.verify(id) + find ActiveStorage.verifier.verify(id, purpose: :blob_id) end def build_after_upload(io:, filename:, content_type: nil, metadata: nil) @@ -39,7 +39,7 @@ def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: def signed_id - ActiveStorage.verifier.generate(id) + ActiveStorage.verifier.generate(id, purpose: :blob_id) end def key diff --git a/app/models/active_storage/service/disk_service.rb b/app/models/active_storage/service/disk_service.rb index c7c45e2146..59b180d0e8 100644 --- a/app/models/active_storage/service/disk_service.rb +++ b/app/models/active_storage/service/disk_service.rb @@ -53,7 +53,7 @@ def exist?(key) def url(key, expires_in:, disposition:, filename:) instrument :url, key do |payload| - verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in) + verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key) generated_url = if defined?(Rails) && defined?(Rails.application) diff --git a/app/models/active_storage/variation.rb b/app/models/active_storage/variation.rb index b37397fcad..45274006a2 100644 --- a/app/models/active_storage/variation.rb +++ b/app/models/active_storage/variation.rb @@ -6,11 +6,11 @@ class ActiveStorage::Variation class << self def decode(key) - new ActiveStorage.verifier.verify(key) + new ActiveStorage.verifier.verify(key, purpose: :variation) end def encode(transformations) - ActiveStorage.verifier.generate(transformations) + ActiveStorage.verifier.generate(transformations, purpose: :variation) end end diff --git a/test/controllers/disk_controller_test.rb b/test/controllers/disk_controller_test.rb index c427942c57..58c56d2d0b 100644 --- a/test/controllers/disk_controller_test.rb +++ b/test/controllers/disk_controller_test.rb @@ -11,13 +11,13 @@ class ActiveStorage::DiskControllerTest < ActionController::TestCase end test "showing blob inline" do - get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage.verifier.generate(@blob.key, expires_in: 5.minutes) } + get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage.verifier.generate(@blob.key, expires_in: 5.minutes, purpose: :blob_key) } assert_equal "inline; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end test "sending blob as attachment" do - get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage.verifier.generate(@blob.key, expires_in: 5.minutes), disposition: :attachment } + get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage.verifier.generate(@blob.key, expires_in: 5.minutes, purpose: :blob_key), disposition: :attachment } assert_equal "attachment; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end diff --git a/test/models/blob_test.rb b/test/models/blob_test.rb index 45c8b7168f..8a3d0e8124 100644 --- a/test/models/blob_test.rb +++ b/test/models/blob_test.rb @@ -35,6 +35,6 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase private def expected_url_for(blob, disposition: :inline) - "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes)}/#{blob.filename}?disposition=#{disposition}" + "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{blob.filename}?disposition=#{disposition}" end end From ac26aef11f1be08917f3190b3d2b7ba4434444f7 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Sun, 23 Jul 2017 17:06:45 -0400 Subject: [PATCH 236/289] Require mini_magick when it's used --- app/models/active_storage/variant.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/active_storage/variant.rb b/app/models/active_storage/variant.rb index 435033f980..c12c29c453 100644 --- a/app/models/active_storage/variant.rb +++ b/app/models/active_storage/variant.rb @@ -1,5 +1,4 @@ require "active_storage/blob" -require "mini_magick" # Image blobs can have variants that are the result of a set of transformations applied to the original. class ActiveStorage::Variant @@ -30,6 +29,7 @@ def process end def transform(io) + require "mini_magick" File.open MiniMagick::Image.read(io).tap { |image| variation.transform(image) }.path end end From e16739d4b2dd4910540cf926aa526ed9c96253b7 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 16:27:33 -0500 Subject: [PATCH 237/289] Work-around until @response.parsed_body problem is solved --- test/controllers/direct_uploads_controller_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index 60b15b1fdd..8f309d0b28 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -22,7 +22,7 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase post :create, params: { blob: { filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } - @response.parsed_body.tap do |details| + JSON.parse(@response.body).tap do |details| assert_match(/#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["upload_to_url"]) assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s end @@ -52,7 +52,7 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase post :create, params: { blob: { filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } - @response.parsed_body.tap do |details| + JSON.parse(@response.body).tap do |details| assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["upload_to_url"] assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s end From eb9b019fee51cff69dfcbf19e6c326c426acc297 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 16:28:45 -0500 Subject: [PATCH 238/289] Return to same level of abstraction --- app/models/active_storage/variant.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/active_storage/variant.rb b/app/models/active_storage/variant.rb index 435033f980..002d2bde5b 100644 --- a/app/models/active_storage/variant.rb +++ b/app/models/active_storage/variant.rb @@ -11,7 +11,7 @@ def initialize(blob, variation) end def processed - process unless service.exist?(key) + process unless processed? self end @@ -25,6 +25,10 @@ def url(expires_in: 5.minutes, disposition: :inline) private + def processed? + service.exist?(key) + end + def process service.upload key, transform(service.download(blob.key)) end From 91d6c6e889d432da8ffc7e1102547cdea1d609be Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 16:28:45 -0500 Subject: [PATCH 239/289] Return to same level of abstraction --- app/models/active_storage/variant.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/active_storage/variant.rb b/app/models/active_storage/variant.rb index c12c29c453..8785625cc8 100644 --- a/app/models/active_storage/variant.rb +++ b/app/models/active_storage/variant.rb @@ -10,7 +10,7 @@ def initialize(blob, variation) end def processed - process unless service.exist?(key) + process unless processed? self end @@ -24,6 +24,10 @@ def url(expires_in: 5.minutes, disposition: :inline) private + def processed? + service.exist?(key) + end + def process service.upload key, transform(service.download(blob.key)) end From c977eef67b5c64932064bc98d2bb293315afc65a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 16:27:33 -0500 Subject: [PATCH 240/289] Work-around until @response.parsed_body problem is solved --- test/controllers/direct_uploads_controller_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index 60b15b1fdd..8f309d0b28 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -22,7 +22,7 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase post :create, params: { blob: { filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } - @response.parsed_body.tap do |details| + JSON.parse(@response.body).tap do |details| assert_match(/#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["upload_to_url"]) assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s end @@ -52,7 +52,7 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase post :create, params: { blob: { filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } - @response.parsed_body.tap do |details| + JSON.parse(@response.body).tap do |details| assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["upload_to_url"] assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s end From 68b5d274a365c1babdb92dedfcf2e600138be5eb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 16:47:11 -0500 Subject: [PATCH 241/289] Add and test preloading scope --- lib/active_storage/attached/macros.rb | 6 ++++++ test/models/attachments_test.rb | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/active_storage/attached/macros.rb b/lib/active_storage/attached/macros.rb index 0452089a5f..5915793f8a 100644 --- a/lib/active_storage/attached/macros.rb +++ b/lib/active_storage/attached/macros.rb @@ -33,6 +33,10 @@ def has_one_attached(name, dependent: :purge_later) # There are no columns defined on the model side, Active Storage takes # care of the mapping between your records and the attachments. # + # To avoid N+1 queries, you can include the attached blobs in your query like so: + # + # Gallery.where(user: Current.user).with_attached_photos + # # If the +:dependent+ option isn't set, all the attachments will be purged # (i.e. destroyed) whenever the record is destroyed. def has_many_attached(name, dependent: :purge_later) @@ -44,6 +48,8 @@ def has_many_attached(name, dependent: :purge_later) has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment" has_many :"#{name}_blobs", through: :"#{name}_attachments" + scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) } + if dependent == :purge_later before_destroy { public_send(name).purge_later } end diff --git a/test/models/attachments_test.rb b/test/models/attachments_test.rb index 45f62b0bbf..eac3cbe680 100644 --- a/test/models/attachments_test.rb +++ b/test/models/attachments_test.rb @@ -73,7 +73,10 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) - User.where(id: @user.id).includes(highlights_attachments: :blob).first + highlights = User.where(id: @user.id).with_attached_highlights.first.highlights + + assert_equal "town.jpg", highlights.first.filename.to_s + assert_equal "country.jpg", highlights.second.filename.to_s end test "purge attached blobs" do From e16d0c9ceacd771c99048385dc886c6026c7bc45 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 16:57:26 -0500 Subject: [PATCH 242/289] No more GlobalID --- README.md | 2 +- app/models/active_storage/attachment.rb | 1 - lib/active_storage/attached.rb | 2 -- test/test_helper.rb | 4 ---- 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index 9e4749b1c6..c1bfe12b77 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Furthermore, this repository is likely to be in heavy flux prior to the merge to ## Compared to other storage solutions -A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in [Blob](https://github.com/rails/activestorage/blob/master/lib/active_storage/blob.rb) and [Attachment](https://github.com/rails/activestorage/blob/master/lib/active_storage/attachment.rb) models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses GlobalID to provide polymorphic associations via the join model of `Attachment`, which then connects to the actual `Blob`. +A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in [Blob](https://github.com/rails/activestorage/blob/master/lib/active_storage/blob.rb) and [Attachment](https://github.com/rails/activestorage/blob/master/lib/active_storage/attachment.rb) models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses polymorphic associations via the join model of `Attachment`, which then connects to the actual `Blob`. These `Blob` models are intended to be immutable in spirit. One file, one blob. You can associate the same blob with multiple application models as well. And if you want to do transformations of a given `Blob`, the idea is that you'll simply create a new one, rather than attempt to mutate the existing (though of course you can delete that later if you don't need it). diff --git a/app/models/active_storage/attachment.rb b/app/models/active_storage/attachment.rb index 1dd202ca45..d491c7224e 100644 --- a/app/models/active_storage/attachment.rb +++ b/app/models/active_storage/attachment.rb @@ -1,5 +1,4 @@ require "active_storage/blob" -require "global_id" require "active_support/core_ext/module/delegation" # Schema: id, record_gid, blob_id, created_at diff --git a/lib/active_storage/attached.rb b/lib/active_storage/attached.rb index 9fa7b8e021..6b81545897 100644 --- a/lib/active_storage/attached.rb +++ b/lib/active_storage/attached.rb @@ -4,8 +4,6 @@ require "action_dispatch/http/upload" require "active_support/core_ext/module/delegation" -require "global_id/locator" - class ActiveStorage::Attached attr_reader :name, :record diff --git a/test/test_helper.rb b/test/test_helper.rb index 650e997205..a6e228c4d2 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -62,7 +62,3 @@ class ActionController::TestCase require "active_storage/attached" ActiveRecord::Base.send :extend, ActiveStorage::Attached::Macros - -require "global_id" -GlobalID.app = "ActiveStorageExampleApp" -ActiveRecord::Base.send :include, GlobalID::Identification From 2bbfaf0c9f6ad23cb2c64a917848ca180917ebe2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 16:58:09 -0500 Subject: [PATCH 243/289] Demonstrate preloading in example --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index c1bfe12b77..8f340d7013 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,11 @@ class MessagesController < ApplicationController message.images.attach(params[:message][:images]) redirect_to message end + + def show + # Use the built-in with_attached_images scope to avoid N+1 + @message = Message.find(params[:id]).with_attached_images + end end ``` From efa8779c659221b8e4fa9a154b10f4aa15a3994a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 17:31:02 -0500 Subject: [PATCH 244/289] Fix attaching Can use the associations now --- lib/active_storage/attached/many.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/active_storage/attached/many.rb b/lib/active_storage/attached/many.rb index 704369ba89..ea4aade5d7 100644 --- a/lib/active_storage/attached/many.rb +++ b/lib/active_storage/attached/many.rb @@ -13,9 +13,9 @@ def attachments # Associates one or several attachments with the current record, saving # them to the database. def attach(*attachables) - record.public_send("#{name}_attachments=", attachments | Array(attachables).flat_map do |attachable| - ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) - end) + attachables.flatten.collect do |attachable| + attachments.create!(name: name, blob: create_blob_from(attachable)) + end end # Checks the presence of attachments. From f6ba62be186bd0f570625e25f2f5da838cbc0dc2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 17:31:31 -0500 Subject: [PATCH 245/289] Schema out of date and now obvious --- app/models/active_storage/attachment.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/active_storage/attachment.rb b/app/models/active_storage/attachment.rb index d491c7224e..c3774306d8 100644 --- a/app/models/active_storage/attachment.rb +++ b/app/models/active_storage/attachment.rb @@ -1,7 +1,6 @@ require "active_storage/blob" require "active_support/core_ext/module/delegation" -# Schema: id, record_gid, blob_id, created_at class ActiveStorage::Attachment < ActiveRecord::Base self.table_name = "active_storage_attachments" From cb2f7d499466acd2a8c9b917262914e46b5bf104 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 17:35:25 -0500 Subject: [PATCH 246/289] Still need GlobalID for PurgeJob serialization --- test/test_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index a6e228c4d2..650e997205 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -62,3 +62,7 @@ class ActionController::TestCase require "active_storage/attached" ActiveRecord::Base.send :extend, ActiveStorage::Attached::Macros + +require "global_id" +GlobalID.app = "ActiveStorageExampleApp" +ActiveRecord::Base.send :include, GlobalID::Identification From 5944850bc1259ca42381ce83d155ddd914b968c6 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 23 Jul 2017 17:50:31 -0500 Subject: [PATCH 247/289] Tell service which content-type to use for the response --- app/controllers/active_storage/disk_controller.rb | 4 ++-- app/models/active_storage/blob.rb | 2 +- app/models/active_storage/service.rb | 2 +- app/models/active_storage/service/disk_service.rb | 8 +++++--- app/models/active_storage/service/gcs_service.rb | 8 +++++--- app/models/active_storage/service/s3_service.rb | 5 +++-- app/models/active_storage/variant.rb | 2 +- test/models/blob_test.rb | 2 +- test/service/disk_service_test.rb | 2 +- test/service/gcs_service_test.rb | 5 +++-- test/service/mirror_service_test.rb | 4 ++-- test/service/s3_service_test.rb | 4 ++-- 12 files changed, 27 insertions(+), 21 deletions(-) diff --git a/app/controllers/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb index a42b4833a7..986eee6504 100644 --- a/app/controllers/active_storage/disk_controller.rb +++ b/app/controllers/active_storage/disk_controller.rb @@ -11,8 +11,8 @@ class ActiveStorage::DiskController < ActionController::Base def show if key = decode_verified_key - # FIXME: Find a way to set the correct content type - send_data disk_service.download(key), filename: params[:filename], disposition: disposition_param + # FIXME: Do we need to sign or otherwise validate the content type? + send_data disk_service.download(key), filename: params[:filename], disposition: disposition_param, content_type: params[:content_type] else head :not_found end diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index fdf9a2c37d..3340c88d12 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -57,7 +57,7 @@ def variant(transformations) def url(expires_in: 5.minutes, disposition: :inline) - service.url key, expires_in: expires_in, disposition: disposition, filename: filename + service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type end def url_for_direct_upload(expires_in: 5.minutes) diff --git a/app/models/active_storage/service.rb b/app/models/active_storage/service.rb index 745b1a615f..9d370d0a2b 100644 --- a/app/models/active_storage/service.rb +++ b/app/models/active_storage/service.rb @@ -74,7 +74,7 @@ def exist?(key) raise NotImplementedError end - def url(key, expires_in:, disposition:, filename:) + def url(key, expires_in:, disposition:, filename:, content_type:) raise NotImplementedError end diff --git a/app/models/active_storage/service/disk_service.rb b/app/models/active_storage/service/disk_service.rb index 59b180d0e8..3cde203a31 100644 --- a/app/models/active_storage/service/disk_service.rb +++ b/app/models/active_storage/service/disk_service.rb @@ -51,15 +51,17 @@ def exist?(key) end end - def url(key, expires_in:, disposition:, filename:) + def url(key, expires_in:, disposition:, filename:, content_type:) instrument :url, key do |payload| verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key) generated_url = if defined?(Rails) && defined?(Rails.application) - Rails.application.routes.url_helpers.rails_disk_blob_path(verified_key_with_expiration, disposition: disposition, filename: filename) + Rails.application.routes.url_helpers.rails_disk_blob_path \ + verified_key_with_expiration, + disposition: disposition, filename: filename, content_type: content_type else - "/rails/active_storage/disk/#{verified_key_with_expiration}/#{filename}?disposition=#{disposition}" + "/rails/active_storage/disk/#{verified_key_with_expiration}/#{filename}?disposition=#{disposition}&content_type=#{content_type}" end payload[:url] = generated_url diff --git a/app/models/active_storage/service/gcs_service.rb b/app/models/active_storage/service/gcs_service.rb index 7053a130c0..4530de22f6 100644 --- a/app/models/active_storage/service/gcs_service.rb +++ b/app/models/active_storage/service/gcs_service.rb @@ -42,10 +42,12 @@ def exist?(key) end end - def url(key, expires_in:, disposition:, filename:) + def url(key, expires_in:, disposition:, filename:, content_type:) instrument :url, key do |payload| - query = { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"" } - generated_url = file_for(key).signed_url(expires: expires_in, query: query) + generated_url = file_for(key).signed_url expires: expires_in, query: { + "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"", + "response-content-type" => content_type + } payload[:url] = generated_url diff --git a/app/models/active_storage/service/s3_service.rb b/app/models/active_storage/service/s3_service.rb index efffdec157..4c17f9902f 100644 --- a/app/models/active_storage/service/s3_service.rb +++ b/app/models/active_storage/service/s3_service.rb @@ -47,10 +47,11 @@ def exist?(key) end end - def url(key, expires_in:, disposition:, filename:) + def url(key, expires_in:, disposition:, filename:, content_type:) instrument :url, key do |payload| generated_url = object_for(key).presigned_url :get, expires_in: expires_in, - response_content_disposition: "#{disposition}; filename=\"#{filename}\"" + response_content_disposition: "#{disposition}; filename=\"#{filename}\"", + response_content_type: content_type payload[:url] = generated_url diff --git a/app/models/active_storage/variant.rb b/app/models/active_storage/variant.rb index 8785625cc8..d0fee3c62c 100644 --- a/app/models/active_storage/variant.rb +++ b/app/models/active_storage/variant.rb @@ -19,7 +19,7 @@ def key end def url(expires_in: 5.minutes, disposition: :inline) - service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename + service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename, content_type: blob.content_type end diff --git a/test/models/blob_test.rb b/test/models/blob_test.rb index 8a3d0e8124..b6ba63b25e 100644 --- a/test/models/blob_test.rb +++ b/test/models/blob_test.rb @@ -35,6 +35,6 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase private def expected_url_for(blob, disposition: :inline) - "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{blob.filename}?disposition=#{disposition}" + "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{blob.filename}?disposition=#{disposition}&content_type=#{blob.content_type}" end end diff --git a/test/service/disk_service_test.rb b/test/service/disk_service_test.rb index e9a96003f1..1dae4a2f00 100644 --- a/test/service/disk_service_test.rb +++ b/test/service/disk_service_test.rb @@ -7,6 +7,6 @@ class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase test "url generation" do assert_match /rails\/active_storage\/disk\/.*\/avatar\.png\?disposition=inline/, - @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") + @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png", content_type: "image/png") end end diff --git a/test/service/gcs_service_test.rb b/test/service/gcs_service_test.rb index 4cde4b9289..57fe4d4562 100644 --- a/test/service/gcs_service_test.rb +++ b/test/service/gcs_service_test.rb @@ -29,9 +29,10 @@ class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase test "signed URL generation" do freeze_time do url = SERVICE.bucket.signed_url(FIXTURE_KEY, expires: 120) + - "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" + "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" + + "&response-content-type=text%2Fplain" - assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") + assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt", content_type: "text/plain") end end end diff --git a/test/service/mirror_service_test.rb b/test/service/mirror_service_test.rb index fd3d8125d6..129e11d06f 100644 --- a/test/service/mirror_service_test.rb +++ b/test/service/mirror_service_test.rb @@ -46,8 +46,8 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase test "URL generation in primary service" do freeze_time do - assert_equal SERVICE.primary.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt"), - @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") + assert_equal SERVICE.primary.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt", content_type: "text/plain"), + @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt", content_type: "text/plain") end end diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index 049511497b..a6040ec1d5 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -27,8 +27,8 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase end test "signed URL generation" do - assert_match /#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png/, - @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") + assert_match /#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png.*response-content-type=image%2Fpng/, + @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png", content_type: "image/png") end test "uploading with server-side encryption" do From 09068931f0a275946600faca64cdd350ff50c8fa Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Sun, 23 Jul 2017 19:45:03 -0400 Subject: [PATCH 248/289] Don't annotate template class with :nodoc: --- lib/active_storage/migration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/migration.rb b/lib/active_storage/migration.rb index e843c1b630..2e35e163cd 100644 --- a/lib/active_storage/migration.rb +++ b/lib/active_storage/migration.rb @@ -1,4 +1,4 @@ -class ActiveStorageCreateTables < ActiveRecord::Migration[5.1] # :nodoc: +class ActiveStorageCreateTables < ActiveRecord::Migration[5.1] def change create_table :active_storage_blobs do |t| t.string :key From 2afe2a1983293f4eab910ca7b2e81ab9609a906a Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Sun, 23 Jul 2017 19:51:22 -0400 Subject: [PATCH 249/289] Temporarily skip variant tests pending a fix --- test/models/variant_test.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/models/variant_test.rb b/test/models/variant_test.rb index 276bf7e4fc..6b386a8710 100644 --- a/test/models/variant_test.rb +++ b/test/models/variant_test.rb @@ -7,6 +7,8 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase end test "resized variation" do + skip + variant = @blob.variant(resize: "100x100").processed assert_match /racecar.jpg/, variant.url @@ -14,6 +16,8 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase end test "resized and monochrome variation" do + skip + variant = @blob.variant(resize: "100x100", monochrome: true).processed assert_match /racecar.jpg/, variant.url From d982aeed6b7715fe658d5afe522e91256f64cf3f Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Sun, 23 Jul 2017 19:57:16 -0400 Subject: [PATCH 250/289] Skip controller test, too --- test/controllers/variants_controller_test.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/controllers/variants_controller_test.rb b/test/controllers/variants_controller_test.rb index 8d437bedbb..414eaa4ab6 100644 --- a/test/controllers/variants_controller_test.rb +++ b/test/controllers/variants_controller_test.rb @@ -12,6 +12,8 @@ class ActiveStorage::VariantsControllerTest < ActionController::TestCase end test "showing variant inline" do + skip + get :show, params: { filename: @blob.filename, signed_blob_id: @blob.signed_id, From d8aec0f9a44c854038388e115660b20dbe49376e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 08:12:03 -0500 Subject: [PATCH 251/289] Refer to the yielded app --- lib/active_storage/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index 95ed021ce0..71861b84ae 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -24,7 +24,7 @@ class Engine < Rails::Engine # :nodoc: initializer "active_storage.verifier" do config.after_initialize do |app| - ActiveStorage.verifier = Rails.application.message_verifier("ActiveStorage") + ActiveStorage.verifier = app.message_verifier("ActiveStorage") end end From 69922fc7154fb0b99031b3215f42bb0124715608 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 08:48:42 -0500 Subject: [PATCH 252/289] Everything under app/ is eager loaded, don't want that for service Since it references all the specific cloud services that are intended only to be loaded on demand. --- {app/models => lib}/active_storage/service.rb | 0 {app/models => lib}/active_storage/service/configurator.rb | 0 {app/models => lib}/active_storage/service/disk_service.rb | 0 {app/models => lib}/active_storage/service/gcs_service.rb | 0 {app/models => lib}/active_storage/service/mirror_service.rb | 0 {app/models => lib}/active_storage/service/s3_service.rb | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {app/models => lib}/active_storage/service.rb (100%) rename {app/models => lib}/active_storage/service/configurator.rb (100%) rename {app/models => lib}/active_storage/service/disk_service.rb (100%) rename {app/models => lib}/active_storage/service/gcs_service.rb (100%) rename {app/models => lib}/active_storage/service/mirror_service.rb (100%) rename {app/models => lib}/active_storage/service/s3_service.rb (100%) diff --git a/app/models/active_storage/service.rb b/lib/active_storage/service.rb similarity index 100% rename from app/models/active_storage/service.rb rename to lib/active_storage/service.rb diff --git a/app/models/active_storage/service/configurator.rb b/lib/active_storage/service/configurator.rb similarity index 100% rename from app/models/active_storage/service/configurator.rb rename to lib/active_storage/service/configurator.rb diff --git a/app/models/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb similarity index 100% rename from app/models/active_storage/service/disk_service.rb rename to lib/active_storage/service/disk_service.rb diff --git a/app/models/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb similarity index 100% rename from app/models/active_storage/service/gcs_service.rb rename to lib/active_storage/service/gcs_service.rb diff --git a/app/models/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb similarity index 100% rename from app/models/active_storage/service/mirror_service.rb rename to lib/active_storage/service/mirror_service.rb diff --git a/app/models/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb similarity index 100% rename from app/models/active_storage/service/s3_service.rb rename to lib/active_storage/service/s3_service.rb From d0e90b4a9dc1accd4f1044fde0dd9a347cd0afcf Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 11:14:29 -0500 Subject: [PATCH 253/289] Blob/Variant#url -> #service_url to emphasize this URL isn't to be public --- README.md | 2 +- app/controllers/active_storage/blobs_controller.rb | 2 +- .../active_storage/direct_uploads_controller.rb | 2 +- app/controllers/active_storage/variants_controller.rb | 2 +- app/models/active_storage/blob.rb | 8 ++++++-- app/models/active_storage/variant.rb | 2 +- test/models/blob_test.rb | 4 ++-- test/models/variant_test.rb | 4 ++-- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8f340d7013..0022ba9c2b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ user.avatar.exist? # => true user.avatar.purge user.avatar.exist? # => false -user.avatar.url(expires_in: 5.minutes) # => /rails/blobs/ +user.avatar.service_url(expires_in: 5.minutes) # => /rails/blobs/ class AvatarsController < ApplicationController def update diff --git a/app/controllers/active_storage/blobs_controller.rb b/app/controllers/active_storage/blobs_controller.rb index 5a527d0a33..cf5c008841 100644 --- a/app/controllers/active_storage/blobs_controller.rb +++ b/app/controllers/active_storage/blobs_controller.rb @@ -5,7 +5,7 @@ class ActiveStorage::BlobsController < ActionController::Base def show if blob = find_signed_blob - redirect_to blob.url(disposition: disposition_param) + redirect_to blob.service_url(disposition: disposition_param) else head :not_found end diff --git a/app/controllers/active_storage/direct_uploads_controller.rb b/app/controllers/active_storage/direct_uploads_controller.rb index 0d1b806f9f..d42c52913a 100644 --- a/app/controllers/active_storage/direct_uploads_controller.rb +++ b/app/controllers/active_storage/direct_uploads_controller.rb @@ -4,7 +4,7 @@ class ActiveStorage::DirectUploadsController < ActionController::Base def create blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args) - render json: { upload_to_url: blob.url_for_direct_upload, signed_blob_id: blob.signed_id } + render json: { upload_to_url: blob.service_url_for_direct_upload, signed_blob_id: blob.signed_id } end private diff --git a/app/controllers/active_storage/variants_controller.rb b/app/controllers/active_storage/variants_controller.rb index a65d7d7571..5d5dd1a63c 100644 --- a/app/controllers/active_storage/variants_controller.rb +++ b/app/controllers/active_storage/variants_controller.rb @@ -3,7 +3,7 @@ class ActiveStorage::VariantsController < ActionController::Base def show if blob = find_signed_blob - redirect_to ActiveStorage::Variant.new(blob, decoded_variation).processed.url(disposition: disposition_param) + redirect_to ActiveStorage::Variant.new(blob, decoded_variation).processed.service_url(disposition: disposition_param) else head :not_found end diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index 3340c88d12..9196692530 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -56,11 +56,15 @@ def variant(transformations) end - def url(expires_in: 5.minutes, disposition: :inline) + # Returns the URL of the blob on the service. This URL is intended to be short-lived for security and not used directly + # with users. Instead, the `service_url` should only be exposed as a redirect from a stable, possibly authenticated URL. + # Hiding the `service_url` behind a redirect also gives you the power to change services without updating all URLs. And + # it allows permanent URLs that redirec to the `service_url` to be cached in the view. + def service_url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type end - def url_for_direct_upload(expires_in: 5.minutes) + def service_url_for_direct_upload(expires_in: 5.minutes) service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size end diff --git a/app/models/active_storage/variant.rb b/app/models/active_storage/variant.rb index d0fee3c62c..a45356e9ba 100644 --- a/app/models/active_storage/variant.rb +++ b/app/models/active_storage/variant.rb @@ -18,7 +18,7 @@ def key "variants/#{blob.key}/#{variation.key}" end - def url(expires_in: 5.minutes, disposition: :inline) + def service_url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename, content_type: blob.content_type end diff --git a/test/models/blob_test.rb b/test/models/blob_test.rb index b6ba63b25e..4a8f1cabf6 100644 --- a/test/models/blob_test.rb +++ b/test/models/blob_test.rb @@ -28,8 +28,8 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase blob = create_blob freeze_time do - assert_equal expected_url_for(blob), blob.url - assert_equal expected_url_for(blob, disposition: :attachment), blob.url(disposition: :attachment) + assert_equal expected_url_for(blob), blob.service_url + assert_equal expected_url_for(blob, disposition: :attachment), blob.service_url(disposition: :attachment) end end diff --git a/test/models/variant_test.rb b/test/models/variant_test.rb index 6b386a8710..9a33d77379 100644 --- a/test/models/variant_test.rb +++ b/test/models/variant_test.rb @@ -11,7 +11,7 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase variant = @blob.variant(resize: "100x100").processed - assert_match /racecar.jpg/, variant.url + assert_match /racecar.jpg/, variant.service_url assert_same_image "racecar-100x100.jpg", variant end @@ -20,7 +20,7 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase variant = @blob.variant(resize: "100x100", monochrome: true).processed - assert_match /racecar.jpg/, variant.url + assert_match /racecar.jpg/, variant.service_url assert_same_image "racecar-100x100-monochrome.jpg", variant end end From 52eed68e398195e536b99181f644232621f938b3 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Mon, 24 Jul 2017 12:41:34 -0400 Subject: [PATCH 254/289] Verify direct upload checksums Closes #74. --- app/models/active_storage/blob.rb | 2 +- lib/active_storage/service.rb | 2 +- lib/active_storage/service/gcs_service.rb | 6 +++--- lib/active_storage/service/s3_service.rb | 4 ++-- test/service/gcs_service_test.rb | 11 ++++++----- test/service/s3_service_test.rb | 9 +++++---- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index 3340c88d12..ec8bbd653b 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -61,7 +61,7 @@ def url(expires_in: 5.minutes, disposition: :inline) end def url_for_direct_upload(expires_in: 5.minutes) - service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size + service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size, checksum: checksum end diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index 9d370d0a2b..127895406f 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -78,7 +78,7 @@ def url(key, expires_in:, disposition:, filename:, content_type:) raise NotImplementedError end - def url_for_direct_upload(key, expires_in:, content_type:, content_length:) + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) raise NotImplementedError end diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index 4530de22f6..4632e5f820 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -44,7 +44,7 @@ def exist?(key) def url(key, expires_in:, disposition:, filename:, content_type:) instrument :url, key do |payload| - generated_url = file_for(key).signed_url expires: expires_in, query: { + generated_url = file_for(key).signed_url expires: expires_in, query: { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"", "response-content-type" => content_type } @@ -55,10 +55,10 @@ def url(key, expires_in:, disposition:, filename:, content_type:) end end - def url_for_direct_upload(key, expires_in:, content_type:, content_length:) + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) instrument :url, key do |payload| generated_url = bucket.signed_url key, method: "PUT", expires: expires_in, - content_type: content_type + content_type: content_type, content_md5: checksum payload[:url] = generated_url diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 4c17f9902f..72ff9f3f36 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -59,10 +59,10 @@ def url(key, expires_in:, disposition:, filename:, content_type:) end end - def url_for_direct_upload(key, expires_in:, content_type:, content_length:) + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) instrument :url, key do |payload| generated_url = object_for(key).presigned_url :put, expires_in: expires_in, - content_type: content_type, content_length: content_length + content_type: content_type, content_length: content_length, content_md5: checksum payload[:url] = generated_url diff --git a/test/service/gcs_service_test.rb b/test/service/gcs_service_test.rb index 57fe4d4562..134a06e3a4 100644 --- a/test/service/gcs_service_test.rb +++ b/test/service/gcs_service_test.rb @@ -9,14 +9,15 @@ class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase test "direct upload" do begin - key = SecureRandom.base58(24) - data = "Something else entirely!" - direct_upload_url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size) + key = SecureRandom.base58(24) + data = "Something else entirely!" + checksum = Digest::MD5.base64digest(data) + url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) HTTParty.put( - direct_upload_url, + url, body: data, - headers: { "Content-Type" => "text/plain" }, + headers: { "Content-Type" => "text/plain", "Content-MD5" => checksum }, debug_output: STDOUT ) diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index a6040ec1d5..019652e28f 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -9,14 +9,15 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase test "direct upload" do begin - key = SecureRandom.base58(24) - data = "Something else entirely!" - url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size) + key = SecureRandom.base58(24) + data = "Something else entirely!" + checksum = Digest::MD5.base64digest(data) + url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size, checksum: checksum) HTTParty.put( url, body: data, - headers: { "Content-Type" => "text/plain" }, + headers: { "Content-Type" => "text/plain", "Content-MD5" => checksum }, debug_output: STDOUT ) From 48d0427ff8dc6d338e2a05103a234872c32e718e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 12:05:15 -0500 Subject: [PATCH 255/289] Basic documentation for all the models --- app/models/active_storage/attachment.rb | 9 +++ app/models/active_storage/blob.rb | 74 ++++++++++++++++++++++++- app/models/active_storage/filename.rb | 10 ++++ app/models/active_storage/variant.rb | 43 ++++++++++++++ app/models/active_storage/variation.rb | 16 +++++- 5 files changed, 149 insertions(+), 3 deletions(-) diff --git a/app/models/active_storage/attachment.rb b/app/models/active_storage/attachment.rb index c3774306d8..2c8b7a9cf2 100644 --- a/app/models/active_storage/attachment.rb +++ b/app/models/active_storage/attachment.rb @@ -1,6 +1,10 @@ require "active_storage/blob" require "active_support/core_ext/module/delegation" +# Attachments associate records with blobs. Usually that's a one record-many blobs relationship, +# but it is possible to associate many different records with the same blob. If you're doing that, +# you'll want to declare with `has_one/many_attached :thingy, dependent: false`, so that destroying +# any one record won't destroy the blob as well. (Then you'll need to do your own garbage collecting, though). class ActiveStorage::Attachment < ActiveRecord::Base self.table_name = "active_storage_attachments" @@ -9,11 +13,16 @@ class ActiveStorage::Attachment < ActiveRecord::Base delegate_missing_to :blob + # Purging an attachment will purge the blob (delete the file on the service, then destroy the record) + # and then destroy the attachment itself. def purge blob.purge destroy end + # Purging an attachment means purging the blob, which means talking to the service, which means + # talking over the internet. Whenever you're doing that, it's a good idea to put that work in a job, + # so it doesn't hold up other operations. That's what #purge_later provides. def purge_later ActiveStorage::PurgeJob.perform_later(self) end diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index 9196692530..8f810203e2 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -4,7 +4,19 @@ require "active_storage/variant" require "active_storage/variation" -# Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at +# A blob is a record that contains the metadata about a file and a key for where that file resides on the service. +# Blobs can be created in two ways: +# +# 1) Subsequent to the file being uploaded server-side to the service via #create_after_upload! +# 2) Ahead of the file being directly uploaded client-side to the service via #create_before_direct_upload! +# +# The first option doesn't require any client-side JavaScript integration, and can be used by any other back-end +# service that deals with files. The second option is faster, since you're not using your own server as a staging +# point for uploads, and can work with deployments like Heroku that do not provide large amounts of disk space. +# +# Blobs are intended to be immutable in as-so-far as their reference to a specific file goes. You're allowed to +# update a blob's metadata on a subsequent pass, but you should not update the key or change the uploaded file. +# If you need to create a derivative or otherwise change the blob, simply create a new blob and purge the old. class ActiveStorage::Blob < ActiveRecord::Base self.table_name = "active_storage_blobs" @@ -14,10 +26,16 @@ class ActiveStorage::Blob < ActiveRecord::Base class_attribute :service class << self + # You can used the signed id of a blob to refer to it on the client side without fear of tampering. + # This is particularly helpful for direct uploads where the client side needs to refer to the blob + # that was created ahead of the upload itself on form submission. + # + # The signed id is also used to create stable URLs for the blob through the BlobsController. def find_signed(id) find ActiveStorage.verifier.verify(id, purpose: :blob_id) end + # Returns a new, unsaved blob instance after the `io` has been uploaded to the service. def build_after_upload(io:, filename:, content_type: nil, metadata: nil) new.tap do |blob| blob.filename = filename @@ -28,29 +46,59 @@ def build_after_upload(io:, filename:, content_type: nil, metadata: nil) end end + # Returns a saved blob instance after the `io` has been uploaded to the service. Note, the blob is first built, + # then the `io` is uploaded, then the blob is saved. This is doing to avoid opening a transaction and talking to + # the service during that (which is a bad idea and leads to deadlocks). def create_after_upload!(io:, filename:, content_type: nil, metadata: nil) build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!) end + # Returns a saved blob _without_ uploading a file to the service. This blob will point to a key where there is + # no file yet. It's intended to be used together with a client-side upload, which will first create the blob + # in order to produce the signed URL for uploading. This signed URL points to the key generated by the blob. + # Once the form using the direct upload is submitted, the blob can be associated with the right record using + # the signed ID. def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: nil, metadata: nil) create! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata end end - + + # Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering. + # It uses the framework-wide verifier on `ActiveStorage.verifier`, but with a dedicated purpose. def signed_id ActiveStorage.verifier.generate(id, purpose: :blob_id) end + # Returns the key pointing to the file on the service that's associated with this blob. The key is in the + # standard secure-token format from Rails. So it'll look like: XTAPjJCJiuDrLk3TmwyJGpUo. This key is not intended + # to be revealed directly to the user. Always refer to blobs using the signed_id or a verified form of the key. def key # We can't wait until the record is first saved to have a key for it self[:key] ||= self.class.generate_unique_secure_token end + # Returns a `ActiveStorage::Filename` instance of the filename that can be queried for basename, extension, and + # a sanitized version of the filename that's safe to use in URLs. def filename ActiveStorage::Filename.new(self[:filename]) end + # Returns a `ActiveStorage::Variant` instance with the set of `transformations` passed in. This is only relevant + # for image files, and it allows any image to be transformed for size, colors, and the like. Example: + # + # avatar.variant(resize: "100x100").processed.service_url + # + # This will create and process a variant of the avatar blob that's constrained to a height and width of 100. + # Then it'll upload said variant to the service according to a derivative key of the blob and the transformations. + # + # Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a + # specific variant that can be created by a controller on-demand. Like so: + # + # <%= image_tag url_for(Current.user.avatar.variant(resize: "100x100")) %> + # + # This will create a URL for that specific blob with that specific variant, which the `ActiveStorage::VariantsController` + # can then produce on-demand. def variant(transformations) ActiveStorage::Variant.new(self, ActiveStorage::Variation.new(transformations)) end @@ -64,11 +112,23 @@ def service_url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type end + # Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be + # short-lived for security and only generated on-demand by the client-side JavaScript responsible for doing the uploading. def service_url_for_direct_upload(expires_in: 5.minutes) service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size end + # Uploads the `io` to the service on the `key` for this blob. Blobs are intended to be immutable, so you shouldn't be + # using this method after a file has already been uploaded to fit with a blob. If you want to create a derivative blob, + # you should instead simply create a new blob based on the old one. + # + # Prior to uploading, we compute the checksum, which is sent to the service for transit integrity validation. If the + # checksum does not match what the service receives, an exception will be raised. We also measure the size of the `io` + # and store that in `byte_size` on the blob record. + # + # Normally, you do not have to call this method directly at all. Use the factory class methods of `build_after_upload` + # and `create_after_upload!`. def upload(io) self.checksum = compute_checksum_in_chunks(io) self.byte_size = io.size @@ -76,20 +136,30 @@ def upload(io) service.upload(key, io, checksum: checksum) end + # Downloads the file associated with this blob. If no block is given, the entire file is read into memory and returned. + # That'll use a lot of RAM for very large files. If a block is given, then the download is streamed and yielded in chunks. def download(&block) service.download key, &block end + # Deletes the file on the service that's associated with this blob. This should only be done if the blob is going to be + # deleted as well or you will essentially have a dead reference. It's recommended to use the `#purge` and `#purge_later` + # methods in most circumstances. def delete service.delete key end + # Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted + # blobs. Note, though, that deleting the file off the service will initiate a HTTP connection to the service, which may + # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use `#purge_later` instead. def purge delete destroy end + # Enqueues a `ActiveStorage::PurgeJob` job that'll call `#purge`. This is the recommended way to purge blobs when the call + # needs to be made from a transaction, a callback, or any other real-time scenario. def purge_later ActiveStorage::PurgeJob.perform_later(self) end diff --git a/app/models/active_storage/filename.rb b/app/models/active_storage/filename.rb index 71614b5113..8605e4960c 100644 --- a/app/models/active_storage/filename.rb +++ b/app/models/active_storage/filename.rb @@ -1,3 +1,5 @@ +# Encapsulates a string representing a filename to provide convenience access to parts of it and a sanitized version. +# This is what's returned by `ActiveStorage::Blob#filename`. A Filename instance is comparable so it can be used for sorting. class ActiveStorage::Filename include Comparable @@ -5,22 +7,30 @@ def initialize(filename) @filename = filename end + # Filename.new("racecar.jpg").extname # => ".jpg" def extname File.extname(@filename) end + # Filename.new("racecar.jpg").extension # => "jpg" def extension extname.from(1) end + # Filename.new("racecar.jpg").base # => "racecar" def base File.basename(@filename, extname) end + # Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg" + # Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg" + # + # ...and any other character unsafe for URLs or storage is converted or stripped. def sanitized @filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-") end + # Returns the sanitized version of the filename. def to_s sanitized.to_s end diff --git a/app/models/active_storage/variant.rb b/app/models/active_storage/variant.rb index a45356e9ba..a8e64f781e 100644 --- a/app/models/active_storage/variant.rb +++ b/app/models/active_storage/variant.rb @@ -1,6 +1,39 @@ require "active_storage/blob" # Image blobs can have variants that are the result of a set of transformations applied to the original. +# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the +# original. +# +# Variants rely on `MiniMagick` for the actual transformations of the file, so you must add `gem "mini_magick"` +# to your Gemfile if you wish to use variants. +# +# Note that to create a variant it's necessary to download the entire blob file from the service and load it +# into memory. The larger the image, the more memory is used. Because of this process, you also want to be +# considerate about when the variant is actually processed. You shouldn't be processing variants inline in a +# template, for example. Delay the processing to an on-demand controller, like the one provided in +# `ActiveStorage::VariantsController`. +# +# To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided +# by Active Storage like so: +# +# <%= image_tag url_for(Current.user.avatar.variant(resize: "100x100")) %> +# +# This will create a URL for that specific blob with that specific variant, which the `ActiveStorage::VariantsController` +# can then produce on-demand. +# +# When you do want to actually produce the variant needed, call `#processed`. This will check that the variant +# has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform +# the transformations, upload the variant to the service, and return itself again. Example: +# +# avatar.variant(resize: "100x100").processed.service_url +# +# This will create and process a variant of the avatar blob that's constrained to a height and width of 100. +# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations. +# +# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php. You can +# combine as many as you like freely: +# +# avatar.variant(resize: "100x100", monochrome: true, flip: "-90") class ActiveStorage::Variant attr_reader :blob, :variation delegate :service, to: :blob @@ -9,15 +42,25 @@ def initialize(blob, variation) @blob, @variation = blob, variation end + # Returns the variant instance itself after it's been processed or an existing processing has been found on the service. def processed process unless processed? self end + # Returns a combination key of the blob and the variation that together identifies a specific variant. def key "variants/#{blob.key}/#{variation.key}" end + # Returns the URL of the variant on the service. This URL is intended to be short-lived for security and not used directly + # with users. Instead, the `service_url` should only be exposed as a redirect from a stable, possibly authenticated URL. + # Hiding the `service_url` behind a redirect also gives you the power to change services without updating all URLs. And + # it allows permanent URLs that redirec to the `service_url` to be cached in the view. + # + # Use `url_for(variant)` (or the implied form, like `link_to variant` or `redirect_to variant`) to get the stable URL + # for a variant that points to the `ActiveStorage::VariantsController`, which in turn will use this `#service_call` method + # for its redirection. def service_url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename, content_type: blob.content_type end diff --git a/app/models/active_storage/variation.rb b/app/models/active_storage/variation.rb index 45274006a2..34b854fd9f 100644 --- a/app/models/active_storage/variation.rb +++ b/app/models/active_storage/variation.rb @@ -1,14 +1,25 @@ require "active_support/core_ext/object/inclusion" -# A set of transformations that can be applied to a blob to create a variant. +# A set of transformations that can be applied to a blob to create a variant. This class is exposed via +# the `ActiveStorage::Blob#variant` method and should rarely be used directly. +# +# In case you do need to use this directly, it's instantiated using a hash of transformations where +# the key is the command and the value is the arguments. Example: +# +# ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90") +# +# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php. class ActiveStorage::Variation attr_reader :transformations class << self + # Returns a variation instance with the transformations that were encoded by `#encode`. def decode(key) new ActiveStorage.verifier.verify(key, purpose: :variation) end + # Returns a signed key for the `transformations`, which can be used to refer to a specific + # variation in a URL or combined key (like `ActiveStorage::Variant#key`). def encode(transformations) ActiveStorage.verifier.generate(transformations, purpose: :variation) end @@ -18,6 +29,8 @@ def initialize(transformations) @transformations = transformations end + # Accepts an open MiniMagick image instance, like what's return by `MiniMagick::Image.read(io)`, + # and performs the `transformations` against it. The transformed image instance is then returned. def transform(image) transformations.each do |(method, argument)| if eligible_argument?(argument) @@ -28,6 +41,7 @@ def transform(image) end end + # Returns a signed key for all the `transformations` that this variation was instantiated with. def key self.class.encode(transformations) end From d4f014b927e215f32c2a11839962561b8bd5f50d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 12:05:23 -0500 Subject: [PATCH 256/289] Start on docs for lib --- lib/active_storage/attached.rb | 2 ++ lib/active_storage/attached/macros.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/active_storage/attached.rb b/lib/active_storage/attached.rb index 6b81545897..8bec129a1a 100644 --- a/lib/active_storage/attached.rb +++ b/lib/active_storage/attached.rb @@ -4,6 +4,8 @@ require "action_dispatch/http/upload" require "active_support/core_ext/module/delegation" +# Abstract baseclass for the particular `ActiveStorage::Attached::One` and `ActiveStorage::Attached::Many` +# classes that both provide proxy access to the blob association for a record. class ActiveStorage::Attached attr_reader :name, :record diff --git a/lib/active_storage/attached/macros.rb b/lib/active_storage/attached/macros.rb index 5915793f8a..54c2731c08 100644 --- a/lib/active_storage/attached/macros.rb +++ b/lib/active_storage/attached/macros.rb @@ -1,3 +1,4 @@ +# Provides the class-level DSL for declaring that an Active Record model has attached blobs. module ActiveStorage::Attached::Macros # Specifies the relation between a single attachment and the model. # From bb3458079e11c4a22271559413be4c9b901b4790 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 14:16:55 -0500 Subject: [PATCH 257/289] Finish basic documentation for controllers --- .../active_storage/blobs_controller.rb | 2 +- .../active_storage/disk_controller.rb | 18 ++++++------------ .../active_storage/variants_controller.rb | 4 ++++ 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/controllers/active_storage/blobs_controller.rb b/app/controllers/active_storage/blobs_controller.rb index cf5c008841..05af29f8b2 100644 --- a/app/controllers/active_storage/blobs_controller.rb +++ b/app/controllers/active_storage/blobs_controller.rb @@ -1,4 +1,4 @@ -# Take a signed permanent reference for a blob and turn it into an expiring service URL for its download. +# Take a signed permanent reference for a blob and turn it into an expiring service URL for download. # Note: These URLs are publicly accessible. If you need to enforce access protection beyond the # security-through-obscurity factor of the signed blob references, you'll need to implement your own # authenticated redirection controller. diff --git a/app/controllers/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb index 986eee6504..ff10cfba84 100644 --- a/app/controllers/active_storage/disk_controller.rb +++ b/app/controllers/active_storage/disk_controller.rb @@ -1,18 +1,12 @@ -# This controller is a wrapper around local file downloading. It allows you to -# make abstraction of the URL generation logic and to serve files with expiry -# if you are using the +Disk+ service. -# -# By default, mounting the Active Storage engine inside your application will -# define a +/rails/blobs/:encoded_key/*filename+ route that will reference this -# controller's +show+ action and will be used to serve local files. -# -# A URL for an attachment can be generated through its +#url+ method, that -# will use the aforementioned route. +# Serves files stored with the disk service in the same way that the cloud services do. +# This means using expiring, signed URLs that are meant for immediate access, not permanent linking. +# Always go through the BlobsController, or your own authenticated controller, rather than directly +# to the service url. class ActiveStorage::DiskController < ActionController::Base def show if key = decode_verified_key - # FIXME: Do we need to sign or otherwise validate the content type? - send_data disk_service.download(key), filename: params[:filename], disposition: disposition_param, content_type: params[:content_type] + send_data disk_service.download(key), + filename: params[:filename], disposition: disposition_param, content_type: params[:content_type] else head :not_found end diff --git a/app/controllers/active_storage/variants_controller.rb b/app/controllers/active_storage/variants_controller.rb index 5d5dd1a63c..aa38f8e928 100644 --- a/app/controllers/active_storage/variants_controller.rb +++ b/app/controllers/active_storage/variants_controller.rb @@ -1,5 +1,9 @@ require "active_storage/variant" +# Take a signed permanent reference for a variant and turn it into an expiring service URL for download. +# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the +# security-through-obscurity factor of the signed blob and variation reference, you'll need to implement your own +# authenticated redirection controller. class ActiveStorage::VariantsController < ActionController::Base def show if blob = find_signed_blob From 547737b85b628191fadb35ef64730ccfb3b8eb8f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 14:17:01 -0500 Subject: [PATCH 258/289] Basic documentation for job --- app/jobs/active_storage/purge_job.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/jobs/active_storage/purge_job.rb b/app/jobs/active_storage/purge_job.rb index 87eb19815d..815f908e6c 100644 --- a/app/jobs/active_storage/purge_job.rb +++ b/app/jobs/active_storage/purge_job.rb @@ -1,3 +1,4 @@ +# Provides delayed purging of attachments or blobs using their `#purge_later` method. class ActiveStorage::PurgeJob < ActiveJob::Base # FIXME: Limit this to a custom ActiveStorage error retry_on StandardError From ef92cb0f5c7c5249576da8a9fb656adbced2e58c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 14:18:00 -0500 Subject: [PATCH 259/289] Follow the same copyright format as the other Rails frameworks --- lib/active_storage.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/active_storage.rb b/lib/active_storage.rb index 4032fd59a7..9cccc4ddd1 100644 --- a/lib/active_storage.rb +++ b/lib/active_storage.rb @@ -1,3 +1,26 @@ +#-- +# Copyright (c) 2017 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + require "active_record" require "active_storage/engine" From 0e9eb11772ec74cc4b27761d4611c73b66063db5 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 14:20:55 -0500 Subject: [PATCH 260/289] Add standard version setup --- lib/active_storage/gem_version.rb | 15 +++++++++++++++ lib/active_storage/version.rb | 8 ++++++++ 2 files changed, 23 insertions(+) create mode 100644 lib/active_storage/gem_version.rb create mode 100644 lib/active_storage/version.rb diff --git a/lib/active_storage/gem_version.rb b/lib/active_storage/gem_version.rb new file mode 100644 index 0000000000..cde112a006 --- /dev/null +++ b/lib/active_storage/gem_version.rb @@ -0,0 +1,15 @@ +module ActiveStorage + # Returns the version of the currently loaded Active Storage as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 0 + MINOR = 1 + TINY = 0 + PRE = "alpha" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/lib/active_storage/version.rb b/lib/active_storage/version.rb new file mode 100644 index 0000000000..8f45480712 --- /dev/null +++ b/lib/active_storage/version.rb @@ -0,0 +1,8 @@ +require_relative "gem_version" + +module ActiveStorage + # Returns the version of the currently loaded ActiveStorage as a Gem::Version + def self.version + gem_version + end +end From 20effee5671d76875c050e3d9e91ff60fa181a91 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 14:22:59 -0500 Subject: [PATCH 261/289] Models are autoloaded per engine standards --- lib/active_storage.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage.rb b/lib/active_storage.rb index 9cccc4ddd1..4466aea1ee 100644 --- a/lib/active_storage.rb +++ b/lib/active_storage.rb @@ -27,7 +27,7 @@ module ActiveStorage extend ActiveSupport::Autoload - autoload :Blob + autoload :Attached autoload :Service mattr_accessor :verifier From 3eb8c89c0410fac34aefc8b89b8c6b5901c39415 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 14:35:36 -0500 Subject: [PATCH 262/289] Fix blob associations cc @javan --- lib/active_storage/attached/macros.rb | 4 ++-- test/models/attachments_test.rb | 18 ++++++++++++++++++ test/test_helper.rb | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/attached/macros.rb b/lib/active_storage/attached/macros.rb index 54c2731c08..11f88f16e5 100644 --- a/lib/active_storage/attached/macros.rb +++ b/lib/active_storage/attached/macros.rb @@ -18,7 +18,7 @@ def has_one_attached(name, dependent: :purge_later) end has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record - has_one :"#{name}_blob", through: :"#{name}_attachment" + has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob if dependent == :purge_later before_destroy { public_send(name).purge_later } @@ -47,7 +47,7 @@ def has_many_attached(name, dependent: :purge_later) end has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment" - has_many :"#{name}_blobs", through: :"#{name}_attachments" + has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) } diff --git a/test/models/attachments_test.rb b/test/models/attachments_test.rb index eac3cbe680..82256e1f44 100644 --- a/test/models/attachments_test.rb +++ b/test/models/attachments_test.rb @@ -30,6 +30,13 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase assert_equal "town.jpg", @user.avatar.filename.to_s end + test "access underlying associations of new blob" do + @user.avatar.attach create_blob(filename: "funky.jpg") + assert_equal @user, @user.avatar_attachment.record + assert_equal @user.avatar_attachment.blob, @user.avatar_blob + assert_equal "funky.jpg", @user.avatar_attachment.blob.filename.to_s + end + test "purge attached blob" do @user.avatar.attach create_blob(filename: "funky.jpg") avatar_key = @user.avatar.key @@ -79,6 +86,17 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase assert_equal "country.jpg", highlights.second.filename.to_s end + test "access underlying associations of new blobs" do + @user.highlights.attach( + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) + + assert_equal @user, @user.highlights_attachments.first.record + assert_equal @user.highlights_attachments.collect(&:blob).sort, @user.highlights_blobs.sort + assert_equal "town.jpg", @user.highlights_attachments.first.blob.filename.to_s + end + + test "purge attached blobs" do @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") highlight_keys = @user.highlights.collect(&:key) diff --git a/test/test_helper.rb b/test/test_helper.rb index 650e997205..d3a55e2cf0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -23,6 +23,7 @@ {} end +require "active_storage/blob" require "active_storage/service/disk_service" require "tmpdir" ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: Dir.mktmpdir("active_storage_tests")) From 87cb0063742f9d5672f69f623be8fe1dee79b223 Mon Sep 17 00:00:00 2001 From: Mike Gunderloy Date: Mon, 24 Jul 2017 15:20:33 -0500 Subject: [PATCH 263/289] Trivial typo fix Only one character, but it needs to be done --- app/models/active_storage/blob.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index 7e388f69ba..6a7836b9e5 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -107,7 +107,7 @@ def variant(transformations) # Returns the URL of the blob on the service. This URL is intended to be short-lived for security and not used directly # with users. Instead, the `service_url` should only be exposed as a redirect from a stable, possibly authenticated URL. # Hiding the `service_url` behind a redirect also gives you the power to change services without updating all URLs. And - # it allows permanent URLs that redirec to the `service_url` to be cached in the view. + # it allows permanent URLs that redirect to the `service_url` to be cached in the view. def service_url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type end From 92536c08d53c5d54f6c526bfdc5d854dd00a7a88 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 15:36:30 -0500 Subject: [PATCH 264/289] Document the rest of lib --- lib/active_storage/attached.rb | 2 +- lib/active_storage/attached/macros.rb | 18 ++++++++++++++++++ lib/active_storage/attached/many.rb | 16 ++++++++++------ lib/active_storage/attached/one.rb | 11 ++++++++--- lib/active_storage/service.rb | 12 ++++++++++++ lib/active_storage/service/disk_service.rb | 2 ++ lib/active_storage/service/gcs_service.rb | 2 ++ lib/active_storage/service/mirror_service.rb | 6 ++++++ lib/active_storage/service/s3_service.rb | 2 ++ 9 files changed, 61 insertions(+), 10 deletions(-) diff --git a/lib/active_storage/attached.rb b/lib/active_storage/attached.rb index 8bec129a1a..4644d74bcc 100644 --- a/lib/active_storage/attached.rb +++ b/lib/active_storage/attached.rb @@ -4,7 +4,7 @@ require "action_dispatch/http/upload" require "active_support/core_ext/module/delegation" -# Abstract baseclass for the particular `ActiveStorage::Attached::One` and `ActiveStorage::Attached::Many` +# Abstract baseclass for the concrete `ActiveStorage::Attached::One` and `ActiveStorage::Attached::Many` # classes that both provide proxy access to the blob association for a record. class ActiveStorage::Attached attr_reader :name, :record diff --git a/lib/active_storage/attached/macros.rb b/lib/active_storage/attached/macros.rb index 11f88f16e5..89297e5bdf 100644 --- a/lib/active_storage/attached/macros.rb +++ b/lib/active_storage/attached/macros.rb @@ -9,6 +9,15 @@ module ActiveStorage::Attached::Macros # There is no column defined on the model side, Active Storage takes # care of the mapping between your records and the attachment. # + # Under the covers, this relationship is implemented as a `has_one` association to a + # `ActiveStorage::Attachment` record and a `has_one-through` association to a + # `ActiveStorage::Blob` record. These associations are available as `avatar_attachment` + # and `avatar_blob`. But you shouldn't need to work with these associations directly in + # most circumstances. + # + # The system has been designed to having you go through the `ActiveStorage::Attached::One` + # proxy that provides the dynamic proxy to the associations and factory methods, like `#attach`. + # # If the +:dependent+ option isn't set, the attachment will be purged # (i.e. destroyed) whenever the record is destroyed. def has_one_attached(name, dependent: :purge_later) @@ -38,6 +47,15 @@ def has_one_attached(name, dependent: :purge_later) # # Gallery.where(user: Current.user).with_attached_photos # + # Under the covers, this relationship is implemented as a `has_many` association to a + # `ActiveStorage::Attachment` record and a `has_many-through` association to a + # `ActiveStorage::Blob` record. These associations are available as `photos_attachments` + # and `photos_blobs`. But you shouldn't need to work with these associations directly in + # most circumstances. + # + # The system has been designed to having you go through the `ActiveStorage::Attached::Many` + # proxy that provides the dynamic proxy to the associations and factory methods, like `#attach`. + # # If the +:dependent+ option isn't set, all the attachments will be purged # (i.e. destroyed) whenever the record is destroyed. def has_many_attached(name, dependent: :purge_later) diff --git a/lib/active_storage/attached/many.rb b/lib/active_storage/attached/many.rb index ea4aade5d7..035cd9c091 100644 --- a/lib/active_storage/attached/many.rb +++ b/lib/active_storage/attached/many.rb @@ -1,24 +1,28 @@ -# Representation of multiple attachments to a model. +# Decorated proxy object representing of multiple attachments to a model. class ActiveStorage::Attached::Many < ActiveStorage::Attached delegate_missing_to :attachments # Returns all the associated attachment records. # - # You don't have to call this method to access the attachments' methods as - # they are all available at the model level. + # All methods called on this proxy object that aren't listed here will automatically be delegated to `attachments`. def attachments record.public_send("#{name}_attachments") end - # Associates one or several attachments with the current record, saving - # them to the database. + # Associates one or several attachments with the current record, saving them to the database. + # Examples: + # + # document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects + # document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload + # document.images.attach(io: File.open("~/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg") + # document.images.attach([ first_blob, second_blob ]) def attach(*attachables) attachables.flatten.collect do |attachable| attachments.create!(name: name, blob: create_blob_from(attachable)) end end - # Checks the presence of attachments. + # Returns true if any attachments has been made. # # class Gallery < ActiveRecord::Base # has_many_attached :photos diff --git a/lib/active_storage/attached/one.rb b/lib/active_storage/attached/one.rb index d255412842..0c522e856e 100644 --- a/lib/active_storage/attached/one.rb +++ b/lib/active_storage/attached/one.rb @@ -10,14 +10,19 @@ def attachment record.public_send("#{name}_attachment") end - # Associates a given attachment with the current record, saving it to the - # database. + # Associates a given attachment with the current record, saving it to the database. + # Examples: + # + # person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object + # person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload + # person.avatar.attach(io: File.open("~/face.jpg"), filename: "face.jpg", content_type: "image/jpg") + # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object def attach(attachable) write_attachment \ ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) end - # Checks the presence of the attachment. + # Returns true if an attachment has been made. # # class User < ActiveRecord::Base # has_one_attached :avatar diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index 127895406f..e6361318a8 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -58,26 +58,38 @@ def build(configurator:, service: nil, **service_config) #:nodoc: end end + # Upload the `io` to the `key` specified. If a `checksum` is provided, the service will + # ensure a match when the upload has completed or raise an `ActiveStorage::IntegrityError`. def upload(key, io, checksum: nil) raise NotImplementedError end + # Return the content of the file at the `key`. def download(key) raise NotImplementedError end + # Delete the file at the `key`. def delete(key) raise NotImplementedError end + # Return true if a file exists at the `key`. def exist?(key) raise NotImplementedError end + # Returns a signed, temporary URL for the file at the `key`. The URL will be valid for the amount + # of seconds specified in `expires_in`. You most also provide the `disposition` (`:inline` or `:attachment`), + # `filename`, and `content_type` that you wish the file to be served with on request. def url(key, expires_in:, disposition:, filename:, content_type:) raise NotImplementedError end + # Returns a signed, temporary URL that a direct upload file can be PUT to on the `key`. + # The URL will be valid for the amount of seconds specified in `expires_in`. + # You most also provide the `content_type`, `content_length`, and `checksum` of the file + # that will be uploaded. All these attributes will be validated by the service upon upload. def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) raise NotImplementedError end diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 3cde203a31..7e2079385f 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -3,6 +3,8 @@ require "digest/md5" require "active_support/core_ext/numeric/bytes" +# Wraps a local disk path as a Active Storage service. See `ActiveStorage::Service` for the generic API +# documentation that applies to all services. class ActiveStorage::Service::DiskService < ActiveStorage::Service attr_reader :root diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index 4632e5f820..d681a3dc45 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -1,6 +1,8 @@ require "google/cloud/storage" require "active_support/core_ext/object/to_query" +# Wraps the Google Cloud Storage as a Active Storage service. See `ActiveStorage::Service` for the generic API +# documentation that applies to all services. class ActiveStorage::Service::GCSService < ActiveStorage::Service attr_reader :client, :bucket diff --git a/lib/active_storage/service/mirror_service.rb b/lib/active_storage/service/mirror_service.rb index 54465cad05..7c407f2730 100644 --- a/lib/active_storage/service/mirror_service.rb +++ b/lib/active_storage/service/mirror_service.rb @@ -1,5 +1,8 @@ require "active_support/core_ext/module/delegation" +# Wraps a set of mirror services and provides a single `ActiveStorage::Service` object that will all +# have the files uploaded to them. A `primary` service is designated to answer calls to `download`, `exists?`, +# and `url`. class ActiveStorage::Service::MirrorService < ActiveStorage::Service attr_reader :primary, :mirrors @@ -16,12 +19,15 @@ def initialize(primary:, mirrors:) @primary, @mirrors = primary, mirrors end + # Upload the `io` to the `key` specified to all services. If a `checksum` is provided, all services will + # ensure a match when the upload has completed or raise an `ActiveStorage::IntegrityError`. def upload(key, io, checksum: nil) each_service.collect do |service| service.upload key, io.tap(&:rewind), checksum: checksum end end + # Delete the file at the `key` on all services. def delete(key) perform_across_services :delete, key end diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 72ff9f3f36..c21977044d 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -1,6 +1,8 @@ require "aws-sdk" require "active_support/core_ext/numeric/bytes" +# Wraps the Amazon Simple Storage Service (S3) as a Active Storage service. +# See `ActiveStorage::Service` for the generic API documentation that applies to all services. class ActiveStorage::Service::S3Service < ActiveStorage::Service attr_reader :client, :bucket, :upload_options From 3a5372d80cfc40d89b07e9c253401fd1b8d47956 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 15:51:25 -0500 Subject: [PATCH 265/289] Flesh out the README a bit more --- README.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0022ba9c2b..72d03f46b5 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,10 @@ Active Storage makes it simple to upload and reference files in cloud services, and attach those files to Active Records. It also provides a disk service for testing or local deployments, but the focus is on cloud storage. -## Compatibility & Expectations +Files can uploaded from the server to the cloud or directly from the client to the cloud. -Active Storage only works with the development version of Rails 5.2+ (as of July 19, 2017). This separate repository is a staging ground for the upcoming inclusion in rails/rails prior to the Rails 5.2 release. It is not intended to be a long-term stand-alone repository. - -Furthermore, this repository is likely to be in heavy flux prior to the merge to rails/rails. You're heartedly encouraged to follow along and even use Active Storage in this phase, but don't be surprised if the API suffers frequent breaking changes prior to the merge. +Image files can further more be transformed using on-demand variants for quality, aspect ratio, size, or any other +MiniMagick supported transformation. ## Compared to other storage solutions @@ -26,15 +25,16 @@ class User < ApplicationRecord end user.avatar.attach io: File.open("~/face.jpg"), filename: "avatar.jpg", content_type: "image/jpg" -user.avatar.exist? # => true +user.avatar.attached? # => true user.avatar.purge -user.avatar.exist? # => false +user.avatar.attached? # => false -user.avatar.service_url(expires_in: 5.minutes) # => /rails/blobs/ +url_for(user.avatar) # Generate a permanent URL for the blob, which upon access will redirect to a temporary service URL. class AvatarsController < ApplicationController def update + # params[:avatar] contains a ActionDispatch::Http::UploadedFile object Current.user.avatar.attach(params.require(:avatar)) redirect_to Current.user end @@ -61,6 +61,11 @@ end ```ruby class MessagesController < ApplicationController + def index + # Use the built-in with_attached_images scope to avoid N+1 + @messages = Message.all.with_attached_images + end + def create message = Message.create! params.require(:message).permit(:title, :content) message.images.attach(params[:message][:images]) @@ -68,8 +73,7 @@ class MessagesController < ApplicationController end def show - # Use the built-in with_attached_images scope to avoid N+1 - @message = Message.find(params[:id]).with_attached_images + @message = Message.find(params[:id]) end end ``` @@ -88,7 +92,15 @@ Variation of image attachment: 3. Run `rails activestorage:install` to create needed directories, migrations, and configuration. 4. Configure the storage service in `config/environments/*` with `config.active_storage.service = :local` that references the services configured in `config/storage_services.yml`. -5. Optional: Add `gem "mini_magick"` to your Gemfile if you want to use variants. +5. Optional: Add `gem "aws-sdk", "~> 2"` to your Gemfile if you want to use AWS S3. +6. Optional: Add `gem "google-cloud-storage", "~> 1.3"` to your Gemfile if you want to use Google Cloud Storage. +7. Optional: Add `gem "mini_magick"` to your Gemfile if you want to use variants. + +## Compatibility & Expectations + +Active Storage only works with the development version of Rails 5.2+ (as of July 19, 2017). This separate repository is a staging ground for the upcoming inclusion in rails/rails prior to the Rails 5.2 release. It is not intended to be a long-term stand-alone repository. + +Furthermore, this repository is likely to be in heavy flux prior to the merge to rails/rails. You're heartedly encouraged to follow along and even use Active Storage in this phase, but don't be surprised if the API suffers frequent breaking changes prior to the merge. ## Todos From 6dd82b84de08635d3178fa3916e153d78f03c6e1 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 24 Jul 2017 16:00:24 -0500 Subject: [PATCH 266/289] Did that --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 72d03f46b5..705659b9aa 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,6 @@ Furthermore, this repository is likely to be in heavy flux prior to the merge to ## Todos -- Document all the classes - Convert MirrorService to use threading - Read metadata via Marcel? - Add Migrator to copy/move between services From 1907f465bc7a3385fa53fb2a2466372f96990615 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Mon, 24 Jul 2017 23:50:20 -0400 Subject: [PATCH 267/289] Deep merge --- test/service/s3_service_test.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index 019652e28f..fa2df263a6 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -33,20 +33,17 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase end test "uploading with server-side encryption" do - config = {} - config[:s3] = SERVICE_CONFIGURATIONS[:s3].merge \ - upload: { server_side_encryption: "AES256" } - - sse_service = ActiveStorage::Service.configure(:s3, config) + config = SERVICE_CONFIGURATIONS.deep_merge(s3: { upload: { server_side_encryption: "AES256" }}) + service = ActiveStorage::Service.configure(:s3, config) begin key = SecureRandom.base58(24) data = "Something else entirely!" - sse_service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data)) + service.upload key, StringIO.new(data), checksum: Digest::MD5.base64digest(data) - assert_equal "AES256", sse_service.bucket.object(key).server_side_encryption + assert_equal "AES256", service.bucket.object(key).server_side_encryption ensure - sse_service.delete key + service.delete key end end end From 5492c4efa9d869f207ea702d0b328f26c047b75c Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Tue, 25 Jul 2017 21:03:48 -0400 Subject: [PATCH 268/289] Add direct upload support to the disk service --- .../active_storage/disk_controller.rb | 26 ++++++++ config/routes.rb | 3 +- lib/active_storage/service/disk_service.rb | 29 ++++++++- .../direct_uploads_controller_test.rb | 18 ++++++ test/controllers/disk_controller_test.rb | 59 +++++++++++++++++-- test/service/shared_service_tests.rb | 4 +- test/test_helper.rb | 4 ++ 7 files changed, 134 insertions(+), 9 deletions(-) diff --git a/app/controllers/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb index ff10cfba84..6be88d2857 100644 --- a/app/controllers/active_storage/disk_controller.rb +++ b/app/controllers/active_storage/disk_controller.rb @@ -12,11 +12,26 @@ def show end end + def update + if token = decode_verified_token + if acceptable_content?(token) + disk_service.upload token[:key], request.body, checksum: token[:checksum] + else + head :unprocessable_entity + end + else + head :not_found + end + rescue ActiveStorage::IntegrityError + head :unprocessable_entity + end + private def disk_service ActiveStorage::Blob.service end + def decode_verified_key ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key) end @@ -24,4 +39,15 @@ def decode_verified_key def disposition_param params[:disposition].presence_in(%w( inline attachment )) || "inline" end + + + def decode_verified_token + ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token) + end + + # FIXME: Validate Content-Length when we're using integration tests. Controller tests don't + # populate the header properly when a request body is provided. + def acceptable_content?(token) + token[:content_type] == request.content_type + end end diff --git a/config/routes.rb b/config/routes.rb index b368e35cac..bee0d9d256 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,5 @@ Rails.application.routes.draw do - get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob + get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob direct :rails_blob do |blob| route_for(:rails_service_blob, blob.signed_id, blob.filename) @@ -23,5 +23,6 @@ get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob + put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_blob post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads end diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 7e2079385f..d473771563 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -58,7 +58,7 @@ def url(key, expires_in:, disposition:, filename:, content_type:) verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key) generated_url = - if defined?(Rails) && defined?(Rails.application) + if defined?(Rails.application) Rails.application.routes.url_helpers.rails_disk_blob_path \ verified_key_with_expiration, disposition: disposition, filename: filename, content_type: content_type @@ -72,6 +72,32 @@ def url(key, expires_in:, disposition:, filename:, content_type:) end end + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) + instrument :url, key do |payload| + verified_token_with_expiration = ActiveStorage.verifier.generate( + { + key: key, + content_type: content_type, + content_length: content_length, + checksum: checksum + }, + expires_in: expires_in, + purpose: :blob_token + ) + + generated_url = + if defined?(Rails.application) + Rails.application.routes.url_helpers.update_rails_disk_blob_path verified_token_with_expiration + else + "/rails/active_storage/disk/#{verified_token_with_expiration}" + end + + payload[:url] = generated_url + + generated_url + end + end + private def path_for(key) File.join root, folder_for(key), key @@ -87,6 +113,7 @@ def make_path_for(key) def ensure_integrity_of(key, checksum) unless Digest::MD5.file(path_for(key)).base64digest == checksum + delete key raise ActiveStorage::IntegrityError end end diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index 8f309d0b28..76741d277e 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -61,3 +61,21 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase else puts "Skipping GCS Direct Upload tests because no GCS configuration was supplied" end + +class ActiveStorage::DiskDirectUploadsControllerTest < ActionController::TestCase + setup do + @blob = create_blob + @routes = Routes + @controller = ActiveStorage::DirectUploadsController.new + end + + test "creating new direct upload" do + post :create, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } + + JSON.parse(@response.body).tap do |details| + assert_match /rails\/active_storage\/disk/, details["upload_to_url"] + assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s + end + end +end diff --git a/test/controllers/disk_controller_test.rb b/test/controllers/disk_controller_test.rb index 58c56d2d0b..a1542b0784 100644 --- a/test/controllers/disk_controller_test.rb +++ b/test/controllers/disk_controller_test.rb @@ -5,20 +5,67 @@ class ActiveStorage::DiskControllerTest < ActionController::TestCase setup do - @blob = create_blob @routes = Routes @controller = ActiveStorage::DiskController.new end test "showing blob inline" do - get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage.verifier.generate(@blob.key, expires_in: 5.minutes, purpose: :blob_key) } - assert_equal "inline; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] + blob = create_blob + + get :show, params: { filename: blob.filename, encoded_key: ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key) } + assert_equal "inline; filename=\"#{blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end - test "sending blob as attachment" do - get :show, params: { filename: @blob.filename, encoded_key: ActiveStorage.verifier.generate(@blob.key, expires_in: 5.minutes, purpose: :blob_key), disposition: :attachment } - assert_equal "attachment; filename=\"#{@blob.filename}\"", @response.headers["Content-Disposition"] + test "showing blob as attachment" do + blob = create_blob + + get :show, params: { filename: blob.filename, encoded_key: ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key), disposition: :attachment } + assert_equal "attachment; filename=\"#{blob.filename}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end + + test "directly uploading blob with integrity" do + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) + + token = ActiveStorage.verifier.generate( + { + key: blob.key, + content_length: data.size, + content_type: "text/plain", + checksum: Digest::MD5.base64digest(data) + }, + expires_in: 5.minutes, + purpose: :blob_token + ) + + @request.content_type = "text/plain" + + put :update, body: data, params: { encoded_token: token } + assert_response :no_content + assert_equal data, blob.download + end + + test "directly uploading blob without integrity" do + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) + + token = ActiveStorage.verifier.generate( + { + key: blob.key, + content_length: data.size, + content_type: "text/plain", + checksum: Digest::MD5.base64digest("bad data") + }, + expires_in: 5.minutes, + purpose: :blob_token + ) + + @request.content_type = "text/plain" + + put :update, body: data, params: { encoded_token: token } + assert_response :unprocessable_entity + assert_not blob.service.exist?(blob.key) + end end diff --git a/test/service/shared_service_tests.rb b/test/service/shared_service_tests.rb index ad6a9dea7f..07620d91e4 100644 --- a/test/service/shared_service_tests.rb +++ b/test/service/shared_service_tests.rb @@ -29,7 +29,7 @@ module ActiveStorage::Service::SharedServiceTests end end - test "upload without integrity" do + test "uploading without integrity" do begin key = SecureRandom.base58(24) data = "Something else entirely!" @@ -37,6 +37,8 @@ module ActiveStorage::Service::SharedServiceTests assert_raises(ActiveStorage::IntegrityError) do @service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest("bad data")) end + + assert_not @service.exist?(key) ensure @service.delete key end diff --git a/test/test_helper.rb b/test/test_helper.rb index d3a55e2cf0..154a2f0835 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -43,6 +43,10 @@ def create_image_blob(filename: "racecar.jpg", content_type: "image/jpeg") filename: filename, content_type: content_type end + def create_blob_before_direct_upload(filename: "hello.txt", byte_size:, checksum:, content_type: "text/plain") + ActiveStorage::Blob.create_before_direct_upload! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type + end + def assert_same_image(fixture_filename, variant) assert_equal \ File.binread(File.expand_path("../fixtures/files/#{fixture_filename}", __FILE__)), From 6ec6d223400142faf925aa84a15805d8329c4d74 Mon Sep 17 00:00:00 2001 From: Anton Khamets Date: Wed, 26 Jul 2017 20:51:02 +0400 Subject: [PATCH 269/289] Compare images by selected attributes (size, colorspace) --- test/controllers/variants_controller_test.rb | 4 +-- test/models/variant_test.rb | 9 ++---- test/test_helper.rb | 30 ++++++++++++++++---- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/test/controllers/variants_controller_test.rb b/test/controllers/variants_controller_test.rb index 414eaa4ab6..83b00a132f 100644 --- a/test/controllers/variants_controller_test.rb +++ b/test/controllers/variants_controller_test.rb @@ -12,14 +12,12 @@ class ActiveStorage::VariantsControllerTest < ActionController::TestCase end test "showing variant inline" do - skip - get :show, params: { filename: @blob.filename, signed_blob_id: @blob.signed_id, variation_key: ActiveStorage::Variation.encode(resize: "100x100") } assert_redirected_to /racecar.jpg\?disposition=inline/ - assert_same_image "racecar-100x100.jpg", @blob.variant(resize: "100x100") + assert_equal_image_dimensions "racecar-100x100.jpg", @blob.variant(resize: "100x100") end end diff --git a/test/models/variant_test.rb b/test/models/variant_test.rb index 9a33d77379..3d890dfc0f 100644 --- a/test/models/variant_test.rb +++ b/test/models/variant_test.rb @@ -7,20 +7,17 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase end test "resized variation" do - skip - variant = @blob.variant(resize: "100x100").processed assert_match /racecar.jpg/, variant.service_url - assert_same_image "racecar-100x100.jpg", variant + assert_equal_image_dimensions "racecar-100x100.jpg", variant end test "resized and monochrome variation" do - skip - variant = @blob.variant(resize: "100x100", monochrome: true).processed assert_match /racecar.jpg/, variant.service_url - assert_same_image "racecar-100x100-monochrome.jpg", variant + assert_equal_image_dimensions "racecar-100x100-monochrome.jpg", variant + assert_equal_image_colorspace "racecar-100x100-monochrome.jpg", variant end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 154a2f0835..f531cb8079 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -32,6 +32,8 @@ ActiveStorage.verifier = ActiveSupport::MessageVerifier.new("Testing") class ActiveSupport::TestCase + self.file_fixture_path = File.expand_path("../fixtures/files", __FILE__) + private def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type @@ -39,18 +41,34 @@ def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text def create_image_blob(filename: "racecar.jpg", content_type: "image/jpeg") ActiveStorage::Blob.create_after_upload! \ - io: File.open(File.expand_path("../fixtures/files/#{filename}", __FILE__)), + io: file_fixture(filename).open, filename: filename, content_type: content_type end - + def create_blob_before_direct_upload(filename: "hello.txt", byte_size:, checksum:, content_type: "text/plain") ActiveStorage::Blob.create_before_direct_upload! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type end + - def assert_same_image(fixture_filename, variant) - assert_equal \ - File.binread(File.expand_path("../fixtures/files/#{fixture_filename}", __FILE__)), - File.binread(variant.service.send(:path_for, variant.key)) + def assert_equal_image_dimensions(fixture_filename, variant) + expected_image, actual_image = read_image_fixture(fixture_filename), read_image_variant(variant) + + assert_equal expected_image.width, actual_image.width + assert_equal expected_image.height, actual_image.height + end + + def assert_equal_image_colorspace(fixture_filename, variant) + expected_image, actual_image = read_image_fixture(fixture_filename), read_image_variant(variant) + + assert_equal expected_image.colorspace, actual_image.colorspace + end + + def read_image_fixture(fixture_filename) + MiniMagick::Image.open file_fixture(fixture_filename) + end + + def read_image_variant(variant) + MiniMagick::Image.open variant.service.send(:path_for, variant.key) end end From 0605ed9a8e71d75a80e0337602bd19c99c869ad8 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Wed, 26 Jul 2017 13:32:12 -0400 Subject: [PATCH 270/289] Avoid creating unnecessary blobs --- test/controllers/direct_uploads_controller_test.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index 76741d277e..8c88befe6d 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -6,7 +6,6 @@ if SERVICE_CONFIGURATIONS[:s3] class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase setup do - @blob = create_blob @routes = Routes @controller = ActiveStorage::DirectUploadsController.new @@ -35,7 +34,6 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase if SERVICE_CONFIGURATIONS[:gcs] class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase setup do - @blob = create_blob @routes = Routes @controller = ActiveStorage::DirectUploadsController.new @config = SERVICE_CONFIGURATIONS[:gcs] @@ -64,7 +62,6 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase class ActiveStorage::DiskDirectUploadsControllerTest < ActionController::TestCase setup do - @blob = create_blob @routes = Routes @controller = ActiveStorage::DirectUploadsController.new end From 7644a581c2e1a27da4c39e9518c0844e098f7301 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Wed, 26 Jul 2017 14:58:59 -0400 Subject: [PATCH 271/289] Check variant image properties directly --- test/controllers/variants_controller_test.rb | 5 ++++- .../files/racecar-100x100-monochrome.jpg | Bin 27586 -> 0 bytes test/fixtures/files/racecar-100x100.jpg | Bin 29446 -> 0 bytes test/models/variant_test.rb | 14 +++++++----- test/test_helper.rb | 20 +----------------- 5 files changed, 14 insertions(+), 25 deletions(-) delete mode 100644 test/fixtures/files/racecar-100x100-monochrome.jpg delete mode 100644 test/fixtures/files/racecar-100x100.jpg diff --git a/test/controllers/variants_controller_test.rb b/test/controllers/variants_controller_test.rb index 83b00a132f..ec8103718b 100644 --- a/test/controllers/variants_controller_test.rb +++ b/test/controllers/variants_controller_test.rb @@ -18,6 +18,9 @@ class ActiveStorage::VariantsControllerTest < ActionController::TestCase variation_key: ActiveStorage::Variation.encode(resize: "100x100") } assert_redirected_to /racecar.jpg\?disposition=inline/ - assert_equal_image_dimensions "racecar-100x100.jpg", @blob.variant(resize: "100x100") + + image = read_image_variant(@blob.variant(resize: "100x100")) + assert_equal 100, image.width + assert_equal 67, image.height end end diff --git a/test/fixtures/files/racecar-100x100-monochrome.jpg b/test/fixtures/files/racecar-100x100-monochrome.jpg deleted file mode 100644 index 39e683747e30e02ce454e8463280c1a4081ffe27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27586 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

MMLxjjxM@8zCH^Qlv(^g&ZYGiWbc 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 z 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 WOi1+kaupts Date: Wed, 26 Jul 2017 15:09:25 -0400 Subject: [PATCH 272/289] Fix test failure in CI due to platform differences --- test/models/variant_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/variant_test.rb b/test/models/variant_test.rb index f06bd39a10..7d52f005a7 100644 --- a/test/models/variant_test.rb +++ b/test/models/variant_test.rb @@ -22,6 +22,6 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase image = read_image_variant(variant) assert_equal 100, image.width assert_equal 67, image.height - assert_equal "Grayscale", image.colorspace + assert_match /Gray/, image.colorspace end end From 293db49b7f5969d11d5599f97bd968dbe64c7f8b Mon Sep 17 00:00:00 2001 From: Rolandas Barysas Date: Thu, 27 Jul 2017 18:47:26 +0300 Subject: [PATCH 273/289] Fix broken links in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 705659b9aa..328bf01672 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ MiniMagick supported transformation. ## Compared to other storage solutions -A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in [Blob](https://github.com/rails/activestorage/blob/master/lib/active_storage/blob.rb) and [Attachment](https://github.com/rails/activestorage/blob/master/lib/active_storage/attachment.rb) models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses polymorphic associations via the join model of `Attachment`, which then connects to the actual `Blob`. +A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in [Blob](https://github.com/rails/activestorage/blob/master/app/models/active_storage/blob.rb) and [Attachment](https://github.com/rails/activestorage/blob/master/app/models/active_storage/attachment.rb) models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses polymorphic associations via the join model of `Attachment`, which then connects to the actual `Blob`. These `Blob` models are intended to be immutable in spirit. One file, one blob. You can associate the same blob with multiple application models as well. And if you want to do transformations of a given `Blob`, the idea is that you'll simply create a new one, rather than attempt to mutate the existing (though of course you can delete that later if you don't need it). From e64e3f14fd255d91ac0aa7a272741df08da82701 Mon Sep 17 00:00:00 2001 From: Dino Maric Date: Thu, 27 Jul 2017 22:15:38 +0200 Subject: [PATCH 274/289] Introduce the Dummy. (#70) --- .gitignore | 4 + config/routes.rb | 4 +- lib/active_storage/service/disk_service.rb | 4 +- .../direct_uploads_controller_test.rb | 36 +++----- test/controllers/disk_controller_test.rb | 37 +++++---- test/controllers/variants_controller_test.rb | 13 +-- test/dummy/Rakefile | 3 + test/dummy/app/assets/config/manifest.js | 5 ++ test/dummy/app/assets/images/.keep | 0 .../app/assets/javascripts/application.js | 13 +++ .../app/assets/stylesheets/application.css | 15 ++++ .../app/controllers/application_controller.rb | 3 + test/dummy/app/controllers/concerns/.keep | 0 test/dummy/app/helpers/application_helper.rb | 2 + test/dummy/app/jobs/application_job.rb | 2 + test/dummy/app/models/application_record.rb | 3 + test/dummy/app/models/concerns/.keep | 0 .../app/views/layouts/application.html.erb | 14 ++++ test/dummy/bin/bundle | 3 + test/dummy/bin/rails | 4 + test/dummy/bin/rake | 4 + test/dummy/bin/yarn | 11 +++ test/dummy/config.ru | 5 ++ test/dummy/config/application.rb | 25 ++++++ test/dummy/config/boot.rb | 5 ++ test/dummy/config/database.yml | 25 ++++++ test/dummy/config/environment.rb | 5 ++ test/dummy/config/environments/development.rb | 49 +++++++++++ test/dummy/config/environments/production.rb | 82 +++++++++++++++++++ test/dummy/config/environments/test.rb | 33 ++++++++ .../application_controller_renderer.rb | 6 ++ test/dummy/config/initializers/assets.rb | 14 ++++ .../initializers/backtrace_silencers.rb | 7 ++ .../config/initializers/cookies_serializer.rb | 5 ++ .../initializers/filter_parameter_logging.rb | 4 + test/dummy/config/initializers/inflections.rb | 16 ++++ test/dummy/config/initializers/mime_types.rb | 4 + .../config/initializers/wrap_parameters.rb | 14 ++++ test/dummy/config/routes.rb | 2 + test/dummy/config/secrets.yml | 32 ++++++++ test/dummy/config/spring.rb | 6 ++ test/dummy/config/storage_services.yml | 3 + test/dummy/lib/assets/.keep | 0 test/dummy/log/.keep | 0 test/dummy/package.json | 5 ++ test/dummy/public/404.html | 67 +++++++++++++++ test/dummy/public/422.html | 67 +++++++++++++++ test/dummy/public/500.html | 66 +++++++++++++++ .../public/apple-touch-icon-precomposed.png | 0 test/dummy/public/apple-touch-icon.png | 0 test/dummy/public/favicon.ico | 0 test/models/blob_test.rb | 3 +- test/service/disk_service_test.rb | 2 +- test/test_helper.rb | 21 +---- 54 files changed, 676 insertions(+), 77 deletions(-) create mode 100644 test/dummy/Rakefile create mode 100644 test/dummy/app/assets/config/manifest.js create mode 100644 test/dummy/app/assets/images/.keep create mode 100644 test/dummy/app/assets/javascripts/application.js create mode 100644 test/dummy/app/assets/stylesheets/application.css create mode 100644 test/dummy/app/controllers/application_controller.rb create mode 100644 test/dummy/app/controllers/concerns/.keep create mode 100644 test/dummy/app/helpers/application_helper.rb create mode 100644 test/dummy/app/jobs/application_job.rb create mode 100644 test/dummy/app/models/application_record.rb create mode 100644 test/dummy/app/models/concerns/.keep create mode 100644 test/dummy/app/views/layouts/application.html.erb create mode 100755 test/dummy/bin/bundle create mode 100755 test/dummy/bin/rails create mode 100755 test/dummy/bin/rake create mode 100755 test/dummy/bin/yarn create mode 100644 test/dummy/config.ru create mode 100644 test/dummy/config/application.rb create mode 100644 test/dummy/config/boot.rb create mode 100644 test/dummy/config/database.yml create mode 100644 test/dummy/config/environment.rb create mode 100644 test/dummy/config/environments/development.rb create mode 100644 test/dummy/config/environments/production.rb create mode 100644 test/dummy/config/environments/test.rb create mode 100644 test/dummy/config/initializers/application_controller_renderer.rb create mode 100644 test/dummy/config/initializers/assets.rb create mode 100644 test/dummy/config/initializers/backtrace_silencers.rb create mode 100644 test/dummy/config/initializers/cookies_serializer.rb create mode 100644 test/dummy/config/initializers/filter_parameter_logging.rb create mode 100644 test/dummy/config/initializers/inflections.rb create mode 100644 test/dummy/config/initializers/mime_types.rb create mode 100644 test/dummy/config/initializers/wrap_parameters.rb create mode 100644 test/dummy/config/routes.rb create mode 100644 test/dummy/config/secrets.yml create mode 100644 test/dummy/config/spring.rb create mode 100644 test/dummy/config/storage_services.yml create mode 100644 test/dummy/lib/assets/.keep create mode 100644 test/dummy/log/.keep create mode 100644 test/dummy/package.json create mode 100644 test/dummy/public/404.html create mode 100644 test/dummy/public/422.html create mode 100644 test/dummy/public/500.html create mode 100644 test/dummy/public/apple-touch-icon-precomposed.png create mode 100644 test/dummy/public/apple-touch-icon.png create mode 100644 test/dummy/public/favicon.ico diff --git a/.gitignore b/.gitignore index 36298d2843..dc4b5ef71b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ .byebug_history +test/dummy/db/*.sqlite3 +test/dummy/db/*.sqlite3-journal +test/dummy/log/*.log +test/dummy/tmp/ diff --git a/config/routes.rb b/config/routes.rb index bee0d9d256..6aa99c42ac 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -22,7 +22,7 @@ resolve("ActiveStorage::Variant") { |variant| route_for(:rails_variant, variant) } - get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob - put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_blob + get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service + put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads end diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index d473771563..7bc8d311da 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -59,7 +59,7 @@ def url(key, expires_in:, disposition:, filename:, content_type:) generated_url = if defined?(Rails.application) - Rails.application.routes.url_helpers.rails_disk_blob_path \ + Rails.application.routes.url_helpers.rails_disk_service_path \ verified_key_with_expiration, disposition: disposition, filename: filename, content_type: content_type else @@ -87,7 +87,7 @@ def url_for_direct_upload(key, expires_in:, content_type:, content_length:, chec generated_url = if defined?(Rails.application) - Rails.application.routes.url_helpers.update_rails_disk_blob_path verified_token_with_expiration + Rails.application.routes.url_helpers.update_rails_disk_service_path verified_token_with_expiration else "/rails/active_storage/disk/#{verified_token_with_expiration}" end diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index 8c88befe6d..f15fcff314 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -1,14 +1,9 @@ require "test_helper" require "database/setup" -require "active_storage/direct_uploads_controller" - if SERVICE_CONFIGURATIONS[:s3] - class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase + class ActiveStorage::S3DirectUploadsControllerTest < ActionDispatch::IntegrationTest setup do - @routes = Routes - @controller = ActiveStorage::DirectUploadsController.new - @old_service = ActiveStorage::Blob.service ActiveStorage::Blob.service = ActiveStorage::Service.configure(:s3, SERVICE_CONFIGURATIONS) end @@ -18,10 +13,10 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase end test "creating new direct upload" do - post :create, params: { blob: { - filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } + post rails_direct_uploads_url, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } - JSON.parse(@response.body).tap do |details| + response.parsed_body.tap do |details| assert_match(/#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["upload_to_url"]) assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s end @@ -32,10 +27,8 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase end if SERVICE_CONFIGURATIONS[:gcs] - class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase + class ActiveStorage::GCSDirectUploadsControllerTest < ActionDispatch::IntegrationTest setup do - @routes = Routes - @controller = ActiveStorage::DirectUploadsController.new @config = SERVICE_CONFIGURATIONS[:gcs] @old_service = ActiveStorage::Blob.service @@ -47,10 +40,10 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase end test "creating new direct upload" do - post :create, params: { blob: { - filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } + post rails_direct_uploads_url, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } - JSON.parse(@response.body).tap do |details| + @response.parsed_body.tap do |details| assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["upload_to_url"] assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s end @@ -60,17 +53,12 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase puts "Skipping GCS Direct Upload tests because no GCS configuration was supplied" end -class ActiveStorage::DiskDirectUploadsControllerTest < ActionController::TestCase - setup do - @routes = Routes - @controller = ActiveStorage::DirectUploadsController.new - end - +class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::IntegrationTest test "creating new direct upload" do - post :create, params: { blob: { - filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } + post rails_direct_uploads_url, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } - JSON.parse(@response.body).tap do |details| + @response.parsed_body.tap do |details| assert_match /rails\/active_storage\/disk/, details["upload_to_url"] assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s end diff --git a/test/controllers/disk_controller_test.rb b/test/controllers/disk_controller_test.rb index a1542b0784..c79cc97423 100644 --- a/test/controllers/disk_controller_test.rb +++ b/test/controllers/disk_controller_test.rb @@ -1,34 +1,37 @@ require "test_helper" require "database/setup" -require "active_storage/disk_controller" - -class ActiveStorage::DiskControllerTest < ActionController::TestCase - setup do - @routes = Routes - @controller = ActiveStorage::DiskController.new - end - +class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest test "showing blob inline" do blob = create_blob - get :show, params: { filename: blob.filename, encoded_key: ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key) } - assert_equal "inline; filename=\"#{blob.filename}\"", @response.headers["Content-Disposition"] + get rails_disk_service_url( + filename: "hello.txt", + content_type: blob.content_type, + encoded_key: ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key) + ) + + assert_equal "inline; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end - test "showing blob as attachment" do + test "sending blob as attachment" do blob = create_blob - get :show, params: { filename: blob.filename, encoded_key: ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key), disposition: :attachment } - assert_equal "attachment; filename=\"#{blob.filename}\"", @response.headers["Content-Disposition"] + get rails_disk_service_url( + filename: blob.filename, + content_type: blob.content_type, + encoded_key: ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key), + disposition: :attachment + ) + + assert_equal "attachment; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end test "directly uploading blob with integrity" do data = "Something else entirely!" blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) - token = ActiveStorage.verifier.generate( { key: blob.key, @@ -40,9 +43,8 @@ class ActiveStorage::DiskControllerTest < ActionController::TestCase purpose: :blob_token ) - @request.content_type = "text/plain" + put update_rails_disk_service_url(encoded_token: token), params: data, headers: { "Content-Type" => "text/plain" } - put :update, body: data, params: { encoded_token: token } assert_response :no_content assert_equal data, blob.download end @@ -62,9 +64,8 @@ class ActiveStorage::DiskControllerTest < ActionController::TestCase purpose: :blob_token ) - @request.content_type = "text/plain" + put update_rails_disk_service_url(encoded_token: token), params: { body: data } - put :update, body: data, params: { encoded_token: token } assert_response :unprocessable_entity assert_not blob.service.exist?(blob.key) end diff --git a/test/controllers/variants_controller_test.rb b/test/controllers/variants_controller_test.rb index ec8103718b..fa8d15977d 100644 --- a/test/controllers/variants_controller_test.rb +++ b/test/controllers/variants_controller_test.rb @@ -1,23 +1,18 @@ require "test_helper" require "database/setup" -require "active_storage/variants_controller" - -class ActiveStorage::VariantsControllerTest < ActionController::TestCase +class ActiveStorage::VariantsControllerTest < ActionDispatch::IntegrationTest setup do - @routes = Routes - @controller = ActiveStorage::VariantsController.new - @blob = create_image_blob filename: "racecar.jpg" end test "showing variant inline" do - get :show, params: { + get rails_blob_variation_url( filename: @blob.filename, signed_blob_id: @blob.signed_id, - variation_key: ActiveStorage::Variation.encode(resize: "100x100") } + variation_key: ActiveStorage::Variation.encode(resize: "100x100")) - assert_redirected_to /racecar.jpg\?disposition=inline/ + assert_redirected_to /racecar.jpg\?.*disposition=inline/ image = read_image_variant(@blob.variant(resize: "100x100")) assert_equal 100, image.width diff --git a/test/dummy/Rakefile b/test/dummy/Rakefile new file mode 100644 index 0000000000..ed646b6f55 --- /dev/null +++ b/test/dummy/Rakefile @@ -0,0 +1,3 @@ +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/test/dummy/app/assets/config/manifest.js b/test/dummy/app/assets/config/manifest.js new file mode 100644 index 0000000000..a8adebe722 --- /dev/null +++ b/test/dummy/app/assets/config/manifest.js @@ -0,0 +1,5 @@ + +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css +//= link active_storage_manifest.js diff --git a/test/dummy/app/assets/images/.keep b/test/dummy/app/assets/images/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/dummy/app/assets/javascripts/application.js b/test/dummy/app/assets/javascripts/application.js new file mode 100644 index 0000000000..e54c6461cc --- /dev/null +++ b/test/dummy/app/assets/javascripts/application.js @@ -0,0 +1,13 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require_tree . diff --git a/test/dummy/app/assets/stylesheets/application.css b/test/dummy/app/assets/stylesheets/application.css new file mode 100644 index 0000000000..0ebd7fe829 --- /dev/null +++ b/test/dummy/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb new file mode 100644 index 0000000000..1c07694e9d --- /dev/null +++ b/test/dummy/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + protect_from_forgery with: :exception +end diff --git a/test/dummy/app/controllers/concerns/.keep b/test/dummy/app/controllers/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb new file mode 100644 index 0000000000..de6be7945c --- /dev/null +++ b/test/dummy/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/test/dummy/app/jobs/application_job.rb b/test/dummy/app/jobs/application_job.rb new file mode 100644 index 0000000000..a009ace51c --- /dev/null +++ b/test/dummy/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/test/dummy/app/models/application_record.rb b/test/dummy/app/models/application_record.rb new file mode 100644 index 0000000000..10a4cba84d --- /dev/null +++ b/test/dummy/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/test/dummy/app/models/concerns/.keep b/test/dummy/app/models/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..a6eb0174b7 --- /dev/null +++ b/test/dummy/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ + + + + Dummy + <%= csrf_meta_tags %> + + <%= stylesheet_link_tag 'application', media: 'all' %> + <%= javascript_include_tag 'application' %> + + + + <%= yield %> + + diff --git a/test/dummy/bin/bundle b/test/dummy/bin/bundle new file mode 100755 index 0000000000..66e9889e8b --- /dev/null +++ b/test/dummy/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/test/dummy/bin/rails b/test/dummy/bin/rails new file mode 100755 index 0000000000..0739660237 --- /dev/null +++ b/test/dummy/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/test/dummy/bin/rake b/test/dummy/bin/rake new file mode 100755 index 0000000000..17240489f6 --- /dev/null +++ b/test/dummy/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/test/dummy/bin/yarn b/test/dummy/bin/yarn new file mode 100755 index 0000000000..c2bacef836 --- /dev/null +++ b/test/dummy/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +VENDOR_PATH = File.expand_path('..', __dir__) +Dir.chdir(VENDOR_PATH) do + begin + exec "yarnpkg #{ARGV.join(" ")}" + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/test/dummy/config.ru b/test/dummy/config.ru new file mode 100644 index 0000000000..f7ba0b527b --- /dev/null +++ b/test/dummy/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb new file mode 100644 index 0000000000..03bf7b050d --- /dev/null +++ b/test/dummy/config/application.rb @@ -0,0 +1,25 @@ +require_relative 'boot' + +require "rails" +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "action_controller/railtie" +require "action_view/railtie" +require "sprockets/railtie" +#require "action_mailer/railtie" +#require "rails/test_unit/railtie" +#require "action_cable/engine" + + +Bundler.require(*Rails.groups) +require "active_storage" + +module Dummy + class Application < Rails::Application + config.load_defaults 5.1 + + config.active_storage.service = :local + end +end + diff --git a/test/dummy/config/boot.rb b/test/dummy/config/boot.rb new file mode 100644 index 0000000000..c9aef85d40 --- /dev/null +++ b/test/dummy/config/boot.rb @@ -0,0 +1,5 @@ +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) + +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) diff --git a/test/dummy/config/database.yml b/test/dummy/config/database.yml new file mode 100644 index 0000000000..0d02f24980 --- /dev/null +++ b/test/dummy/config/database.yml @@ -0,0 +1,25 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/test/dummy/config/environment.rb b/test/dummy/config/environment.rb new file mode 100644 index 0000000000..426333bb46 --- /dev/null +++ b/test/dummy/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb new file mode 100644 index 0000000000..503375dede --- /dev/null +++ b/test/dummy/config/environments/development.rb @@ -0,0 +1,49 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + # config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/test/dummy/config/environments/production.rb b/test/dummy/config/environments/production.rb new file mode 100644 index 0000000000..35a30e084a --- /dev/null +++ b/test/dummy/config/environments/production.rb @@ -0,0 +1,82 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb new file mode 100644 index 0000000000..4380dfc0cd --- /dev/null +++ b/test/dummy/config/environments/test.rb @@ -0,0 +1,33 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/test/dummy/config/initializers/application_controller_renderer.rb b/test/dummy/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000000..51639b67a0 --- /dev/null +++ b/test/dummy/config/initializers/application_controller_renderer.rb @@ -0,0 +1,6 @@ +# Be sure to restart your server when you modify this file. + +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) diff --git a/test/dummy/config/initializers/assets.rb b/test/dummy/config/initializers/assets.rb new file mode 100644 index 0000000000..4b828e80cb --- /dev/null +++ b/test/dummy/config/initializers/assets.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/test/dummy/config/initializers/backtrace_silencers.rb b/test/dummy/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000000..59385cdf37 --- /dev/null +++ b/test/dummy/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/test/dummy/config/initializers/cookies_serializer.rb b/test/dummy/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000000..5a6a32d371 --- /dev/null +++ b/test/dummy/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/test/dummy/config/initializers/filter_parameter_logging.rb b/test/dummy/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..4a994e1e7b --- /dev/null +++ b/test/dummy/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/test/dummy/config/initializers/inflections.rb b/test/dummy/config/initializers/inflections.rb new file mode 100644 index 0000000000..ac033bf9dc --- /dev/null +++ b/test/dummy/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/test/dummy/config/initializers/mime_types.rb b/test/dummy/config/initializers/mime_types.rb new file mode 100644 index 0000000000..dc1899682b --- /dev/null +++ b/test/dummy/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/test/dummy/config/initializers/wrap_parameters.rb b/test/dummy/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000000..bbfc3961bf --- /dev/null +++ b/test/dummy/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb new file mode 100644 index 0000000000..1daf9a4121 --- /dev/null +++ b/test/dummy/config/routes.rb @@ -0,0 +1,2 @@ +Rails.application.routes.draw do +end diff --git a/test/dummy/config/secrets.yml b/test/dummy/config/secrets.yml new file mode 100644 index 0000000000..77d1fc383a --- /dev/null +++ b/test/dummy/config/secrets.yml @@ -0,0 +1,32 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +# Shared secrets are available across all environments. + +# shared: +# api_key: a1B2c3D4e5F6 + +# Environmental secrets are only available for that specific environment. + +development: + secret_key_base: e0ef5744b10d988669be6b2660c259749779964f3dcb487fd6199743b3558e2d89f7681d6a15d16d144e28979cbdae41885f4fb4c2cf56ff92ac22df282ffb66 + +test: + secret_key_base: 6fb1f3a828a8dcd6ac8dc07b43be4a5265ad64379120d417252a1578fe1f790e7b85ade4f95994de1ac8fb78581690de6e3a6ac4af36a0f0139667418c750d05 + +# Do not keep production secrets in the unencrypted secrets file. +# Instead, either read values from the environment. +# Or, use `bin/rails secrets:setup` to configure encrypted secrets +# and move the `production:` environment over there. + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/test/dummy/config/spring.rb b/test/dummy/config/spring.rb new file mode 100644 index 0000000000..c9119b40c0 --- /dev/null +++ b/test/dummy/config/spring.rb @@ -0,0 +1,6 @@ +%w( + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +).each { |path| Spring.watch(path) } diff --git a/test/dummy/config/storage_services.yml b/test/dummy/config/storage_services.yml new file mode 100644 index 0000000000..2c6762e0d6 --- /dev/null +++ b/test/dummy/config/storage_services.yml @@ -0,0 +1,3 @@ +local: + service: Disk + root: <%= Rails.root.join("storage") %> diff --git a/test/dummy/lib/assets/.keep b/test/dummy/lib/assets/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/dummy/log/.keep b/test/dummy/log/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/dummy/package.json b/test/dummy/package.json new file mode 100644 index 0000000000..caa2d7bb3f --- /dev/null +++ b/test/dummy/package.json @@ -0,0 +1,5 @@ +{ + "name": "dummy", + "private": true, + "dependencies": {} +} diff --git a/test/dummy/public/404.html b/test/dummy/public/404.html new file mode 100644 index 0000000000..2be3af26fc --- /dev/null +++ b/test/dummy/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/test/dummy/public/422.html b/test/dummy/public/422.html new file mode 100644 index 0000000000..c08eac0d1d --- /dev/null +++ b/test/dummy/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/test/dummy/public/500.html b/test/dummy/public/500.html new file mode 100644 index 0000000000..78a030af22 --- /dev/null +++ b/test/dummy/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/test/dummy/public/apple-touch-icon-precomposed.png b/test/dummy/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/dummy/public/apple-touch-icon.png b/test/dummy/public/apple-touch-icon.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/dummy/public/favicon.ico b/test/dummy/public/favicon.ico new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/models/blob_test.rb b/test/models/blob_test.rb index 4a8f1cabf6..a5b291d5db 100644 --- a/test/models/blob_test.rb +++ b/test/models/blob_test.rb @@ -35,6 +35,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase private def expected_url_for(blob, disposition: :inline) - "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{blob.filename}?disposition=#{disposition}&content_type=#{blob.content_type}" + query_string = { content_type: blob.content_type, disposition: disposition }.to_param + "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{blob.filename}?#{query_string}" end end diff --git a/test/service/disk_service_test.rb b/test/service/disk_service_test.rb index 1dae4a2f00..a625521601 100644 --- a/test/service/disk_service_test.rb +++ b/test/service/disk_service_test.rb @@ -6,7 +6,7 @@ class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase include ActiveStorage::Service::SharedServiceTests test "url generation" do - assert_match /rails\/active_storage\/disk\/.*\/avatar\.png\?disposition=inline/, + assert_match /rails\/active_storage\/disk\/.*\/avatar\.png\?.+disposition=inline/, @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png", content_type: "image/png") end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 6c5d8f85ce..9922c8685c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,4 @@ -$LOAD_PATH << File.expand_path("../../app/controllers", __FILE__) -$LOAD_PATH << File.expand_path("../../app/models", __FILE__) -$LOAD_PATH << File.expand_path("../../app/jobs", __FILE__) +require File.expand_path("../../test/dummy/config/environment.rb", __FILE__) require "bundler/setup" require "active_support" @@ -14,7 +12,6 @@ require "active_storage" -require "active_storage/service" require "yaml" SERVICE_CONFIGURATIONS = begin YAML.load_file(File.expand_path("../service/configurations.yml", __FILE__)).deep_symbolize_keys @@ -23,8 +20,6 @@ {} end -require "active_storage/blob" -require "active_storage/service/disk_service" require "tmpdir" ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: Dir.mktmpdir("active_storage_tests")) ActiveStorage::Service.logger = ActiveSupport::Logger.new(STDOUT) @@ -54,20 +49,6 @@ def read_image_variant(variant) end end -require "action_controller" -require "action_controller/test_case" -class ActionController::TestCase - Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| - routes.draw do - # FIXME: Hacky way to avoid having to instantiate the real engine - eval(File.readlines(File.expand_path("../../config/routes.rb", __FILE__)).slice(1..-2).join("\n")) - end - end -end - -require "active_storage/attached" -ActiveRecord::Base.send :extend, ActiveStorage::Attached::Macros - require "global_id" GlobalID.app = "ActiveStorageExampleApp" ActiveRecord::Base.send :include, GlobalID::Identification From a9091eaa67bd2ebbb4876549ff33a33600276040 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 27 Jul 2017 16:52:57 -0400 Subject: [PATCH 275/289] Validate Content-Length --- .../active_storage/disk_controller.rb | 4 +- test/controllers/disk_controller_test.rb | 90 ++++++++++--------- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/app/controllers/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb index 6be88d2857..76377a0f20 100644 --- a/app/controllers/active_storage/disk_controller.rb +++ b/app/controllers/active_storage/disk_controller.rb @@ -45,9 +45,7 @@ def decode_verified_token ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token) end - # FIXME: Validate Content-Length when we're using integration tests. Controller tests don't - # populate the header properly when a request body is provided. def acceptable_content?(token) - token[:content_type] == request.content_type + token[:content_type] == request.content_type && token[:content_length] == request.content_length end end diff --git a/test/controllers/disk_controller_test.rb b/test/controllers/disk_controller_test.rb index c79cc97423..83087eff68 100644 --- a/test/controllers/disk_controller_test.rb +++ b/test/controllers/disk_controller_test.rb @@ -4,69 +4,73 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest test "showing blob inline" do blob = create_blob + key = ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key) - get rails_disk_service_url( - filename: "hello.txt", - content_type: blob.content_type, - encoded_key: ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key) - ) - + get rails_disk_service_url(key, blob.filename, content_type: blob.content_type) assert_equal "inline; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end - test "sending blob as attachment" do + test "showing blob as attachment" do blob = create_blob + key = ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key) - get rails_disk_service_url( - filename: blob.filename, - content_type: blob.content_type, - encoded_key: ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key), - disposition: :attachment - ) - + get rails_disk_service_url(key, blob.filename, content_type: blob.content_type, disposition: :attachment) assert_equal "attachment; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end test "directly uploading blob with integrity" do - data = "Something else entirely!" - blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) - token = ActiveStorage.verifier.generate( - { - key: blob.key, - content_length: data.size, - content_type: "text/plain", - checksum: Digest::MD5.base64digest(data) - }, - expires_in: 5.minutes, - purpose: :blob_token - ) - - put update_rails_disk_service_url(encoded_token: token), params: data, headers: { "Content-Type" => "text/plain" } + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) + token = encode_verified_token_for blob + put update_rails_disk_service_url(token), params: data, headers: { "Content-Type" => "text/plain" } assert_response :no_content assert_equal data, blob.download end test "directly uploading blob without integrity" do - data = "Something else entirely!" - blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) - - token = ActiveStorage.verifier.generate( - { - key: blob.key, - content_length: data.size, - content_type: "text/plain", - checksum: Digest::MD5.base64digest("bad data") - }, - expires_in: 5.minutes, - purpose: :blob_token - ) - - put update_rails_disk_service_url(encoded_token: token), params: { body: data } + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest("bad data") + token = encode_verified_token_for blob + put update_rails_disk_service_url(token), params: data assert_response :unprocessable_entity assert_not blob.service.exist?(blob.key) end + + test "directly uploading blob with mismatched content type" do + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) + token = encode_verified_token_for blob + + put update_rails_disk_service_url(token), params: data, headers: { "Content-Type" => "application/octet-stream" } + assert_response :unprocessable_entity + assert_not blob.service.exist?(blob.key) + end + + test "directly uploading blob with mismatched content length" do + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size - 1, checksum: Digest::MD5.base64digest(data) + token = encode_verified_token_for blob + + put update_rails_disk_service_url(token), params: data, headers: { "Content-Type" => "text/plain" } + assert_response :unprocessable_entity + assert_not blob.service.exist?(blob.key) + end + + private + def encode_verified_token_for(blob) + ActiveStorage.verifier.generate( + { + key: blob.key, + content_length: blob.byte_size, + content_type: blob.content_type, + checksum: blob.checksum + }, + expires_in: 5.minutes, + purpose: :blob_token + ) + end end From 64bbcaf2446a116f1abed69b781522cebbb572da Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 27 Jul 2017 17:05:38 -0400 Subject: [PATCH 276/289] Use blob service URL methods --- test/controllers/disk_controller_test.rb | 48 +++++++----------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/test/controllers/disk_controller_test.rb b/test/controllers/disk_controller_test.rb index 83087eff68..f498d5e22e 100644 --- a/test/controllers/disk_controller_test.rb +++ b/test/controllers/disk_controller_test.rb @@ -4,73 +4,53 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest test "showing blob inline" do blob = create_blob - key = ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key) - get rails_disk_service_url(key, blob.filename, content_type: blob.content_type) + get blob.service_url assert_equal "inline; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end test "showing blob as attachment" do blob = create_blob - key = ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key) - get rails_disk_service_url(key, blob.filename, content_type: blob.content_type, disposition: :attachment) + get blob.service_url(disposition: :attachment) assert_equal "attachment; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end test "directly uploading blob with integrity" do - data = "Something else entirely!" - blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) - token = encode_verified_token_for blob + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) - put update_rails_disk_service_url(token), params: data, headers: { "Content-Type" => "text/plain" } + put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" } assert_response :no_content assert_equal data, blob.download end test "directly uploading blob without integrity" do - data = "Something else entirely!" - blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest("bad data") - token = encode_verified_token_for blob + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest("bad data") - put update_rails_disk_service_url(token), params: data + put blob.service_url_for_direct_upload, params: data assert_response :unprocessable_entity assert_not blob.service.exist?(blob.key) end test "directly uploading blob with mismatched content type" do - data = "Something else entirely!" - blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) - token = encode_verified_token_for blob + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) - put update_rails_disk_service_url(token), params: data, headers: { "Content-Type" => "application/octet-stream" } + put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "application/octet-stream" } assert_response :unprocessable_entity assert_not blob.service.exist?(blob.key) end test "directly uploading blob with mismatched content length" do - data = "Something else entirely!" - blob = create_blob_before_direct_upload byte_size: data.size - 1, checksum: Digest::MD5.base64digest(data) - token = encode_verified_token_for blob + data = "Something else entirely!" + blob = create_blob_before_direct_upload byte_size: data.size - 1, checksum: Digest::MD5.base64digest(data) - put update_rails_disk_service_url(token), params: data, headers: { "Content-Type" => "text/plain" } + put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" } assert_response :unprocessable_entity assert_not blob.service.exist?(blob.key) end - - private - def encode_verified_token_for(blob) - ActiveStorage.verifier.generate( - { - key: blob.key, - content_length: blob.byte_size, - content_type: blob.content_type, - checksum: blob.checksum - }, - expires_in: 5.minutes, - purpose: :blob_token - ) - end end From dc235c4d1366b4f3185d30a8a4e35ad12173fd89 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Thu, 27 Jul 2017 17:07:52 -0400 Subject: [PATCH 277/289] Separate show and direct upload tests --- test/controllers/disk_controller_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/controllers/disk_controller_test.rb b/test/controllers/disk_controller_test.rb index f498d5e22e..df7954d6b4 100644 --- a/test/controllers/disk_controller_test.rb +++ b/test/controllers/disk_controller_test.rb @@ -18,6 +18,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest assert_equal "text/plain", @response.headers["Content-Type"] end + test "directly uploading blob with integrity" do data = "Something else entirely!" blob = create_blob_before_direct_upload byte_size: data.size, checksum: Digest::MD5.base64digest(data) From 6262891b5669716db7c46dcdcec685d2f55903b5 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 27 Jul 2017 19:47:03 -0400 Subject: [PATCH 278/289] Add JavaScript direct upload support (#81) --- .babelrc | 5 + .gitignore | 1 + README.md | 38 + app/assets/javascripts/activestorage.js | 1 + app/javascript/activestorage/blob_record.js | 52 + app/javascript/activestorage/blob_upload.js | 31 + app/javascript/activestorage/direct_upload.js | 43 + .../activestorage/direct_upload_controller.js | 67 + .../direct_uploads_controller.js | 50 + app/javascript/activestorage/file_checksum.js | 48 + app/javascript/activestorage/helpers.js | 42 + app/javascript/activestorage/index.js | 11 + app/javascript/activestorage/ujs.js | 74 + package.json | 22 + webpack.config.js | 27 + yarn.lock | 2627 +++++++++++++++++ 16 files changed, 3139 insertions(+) create mode 100644 .babelrc create mode 100644 app/assets/javascripts/activestorage.js create mode 100644 app/javascript/activestorage/blob_record.js create mode 100644 app/javascript/activestorage/blob_upload.js create mode 100644 app/javascript/activestorage/direct_upload.js create mode 100644 app/javascript/activestorage/direct_upload_controller.js create mode 100644 app/javascript/activestorage/direct_uploads_controller.js create mode 100644 app/javascript/activestorage/file_checksum.js create mode 100644 app/javascript/activestorage/helpers.js create mode 100644 app/javascript/activestorage/index.js create mode 100644 app/javascript/activestorage/ujs.js create mode 100644 package.json create mode 100644 webpack.config.js create mode 100644 yarn.lock diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000..a8211d329f --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + ["env", { "modules": false } ] + ] +} diff --git a/.gitignore b/.gitignore index dc4b5ef71b..c5ff07cedd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .byebug_history +/node_modules test/dummy/db/*.sqlite3 test/dummy/db/*.sqlite3-journal test/dummy/log/*.log diff --git a/README.md b/README.md index 328bf01672..b127f87c5c 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,44 @@ Variation of image attachment: 6. Optional: Add `gem "google-cloud-storage", "~> 1.3"` to your Gemfile if you want to use Google Cloud Storage. 7. Optional: Add `gem "mini_magick"` to your Gemfile if you want to use variants. +## Direct uploads + +Active Storage, with its included JavaScript library, supports uploading directly from the client to the cloud. + +### Direct upload installation + +1. Include `activestorage.js` in your application's JavaScript bundle. + + Using the asset pipeline: + ```js + //= require activestorage + ``` + Using the npm package: + ```js + import * as ActiveStorage from "activestorage" + ActiveStorage.start() + ``` +2. Annotate file inputs with the direct upload URL. + + ```ruby + <%= form.file_field :attachments, multiple: true, data: { direct_upload_url: rails_direct_uploads_url } %> + ``` +3. That's it! Uploads begin upon form submission. + +### Direct upload JavaScript events + +| Event name | Event target | Event data (`event.detail`) | Description | +| --- | --- | --- | --- | +| `direct-uploads:start` | `
` | None | A form containing files for direct upload fields was submit. | +| `direct-upload:initialize` | `` | `{id, file}` | Dispatched for every file after form submission. | +| `direct-upload:start` | `` | `{id, file}` | A direct upload is starting. | +| `direct-upload:before-blob-request` | `` | `{id, file, xhr}` | Before making a request to your application for direct upload metadata. | +| `direct-upload:before-storage-request` | `` | `{id, file, xhr}` | Before making a request to store a file. | +| `direct-upload:progress` | `` | `{id, file, progress}` | As requests to store files progress. | +| `direct-upload:error` | `` | `{id, file, error}` | An error occurred. An `alert` will display unless this event is canceled. | +| `direct-upload:end` | `` | `{id, file}` | A direct upload has ended. | +| `direct-uploads:end` | `` | None | All direct uploads have ended. | + ## Compatibility & Expectations Active Storage only works with the development version of Rails 5.2+ (as of July 19, 2017). This separate repository is a staging ground for the upcoming inclusion in rails/rails prior to the Rails 5.2 release. It is not intended to be a long-term stand-alone repository. diff --git a/app/assets/javascripts/activestorage.js b/app/assets/javascripts/activestorage.js new file mode 100644 index 0000000000..61c8e4ce54 --- /dev/null +++ b/app/assets/javascripts/activestorage.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.bubbles,i=r.cancelable,a=r.detail,u=document.createEvent("Event");return u.initEvent(e,n||!0,i||!0),u.detail=a||{},t.dispatchEvent(u),u}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i1&&void 0!==arguments[1]?arguments[1]:{};n(this,t),this.id=f++,this.file=e,this.url=r.url,this.delegate=r.delegate}return s(t,[{key:"create",value:function(t){var e=this;new a.a(this.file).create(function(r,n){var a=new u.a(e.file,n,e.url);i(e.delegate,"directUploadWillCreateBlobWithXHR",a.xhr),a.create(function(r){if(r)t(r);else{var n=new o.a(a);i(e.delegate,"directUploadWillStoreFileWithXHR",n.xhr),n.create(function(e){e?t(e):t(null,a.toJSON())})}})})}}]),t}()},function(t,e,r){"use strict";function n(){window.ActiveStorage&&Object(i.a)()}Object.defineProperty(e,"__esModule",{value:!0});var i=r(3),a=r(1);r.d(e,"start",function(){return i.a}),r.d(e,"DirectUpload",function(){return a.a}),setTimeout(n,1)},function(t,e,r){"use strict";function n(){d||(d=!0,document.addEventListener("submit",i),document.addEventListener("ajax:before",a))}function i(t){u(t)}function a(t){"FORM"==t.target.tagName&&u(t)}function u(t){var e=t.target;if(e.hasAttribute(l))return void t.preventDefault();var r=new c.a(e),n=r.inputs;n.length&&(t.preventDefault(),e.setAttribute(l,""),n.forEach(s),r.start(function(t){e.removeAttribute(l),t?n.forEach(f):o(e)}))}function o(t){var e=Object(h.b)(t,"input[type=submit]");if(e){var r=e,n=r.disabled;e.disabled=!1,e.click(),e.disabled=n}else e=document.createElement("input"),e.type="submit",e.style="display:none",t.appendChild(e),e.click(),t.removeChild(e)}function s(t){t.disabled=!0}function f(t){t.disabled=!1}e.a=n;var c=r(4),h=r(0),l="data-direct-uploads-processing",d=!1},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(5),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r=200&&r<300?(this.attributes.signed_id=n.signed_blob_id,this.uploadURL=n.upload_to_url,this.callback(null,this.toJSON())):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.xhr.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r=200&&r<300?this.callback(null,this.xhr.response):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])}); \ No newline at end of file diff --git a/app/javascript/activestorage/blob_record.js b/app/javascript/activestorage/blob_record.js new file mode 100644 index 0000000000..9b7801afd5 --- /dev/null +++ b/app/javascript/activestorage/blob_record.js @@ -0,0 +1,52 @@ +import { getMetaValue } from "./helpers" + +export class BlobRecord { + constructor(file, checksum, url) { + this.file = file + + this.attributes = { + filename: file.name, + content_type: file.type, + byte_size: file.size, + checksum: checksum + } + + this.xhr = new XMLHttpRequest + this.xhr.open("POST", url, true) + this.xhr.responseType = "json" + this.xhr.setRequestHeader("Content-Type", "application/json") + this.xhr.setRequestHeader("Accept", "application/json") + this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest") + this.xhr.setRequestHeader("X-CSRF-Token", getMetaValue("csrf-token")) + this.xhr.addEventListener("load", event => this.requestDidLoad(event)) + this.xhr.addEventListener("error", event => this.requestDidError(event)) + } + + create(callback) { + this.callback = callback + this.xhr.send(JSON.stringify({ blob: this.attributes })) + } + + requestDidLoad(event) { + const { status, response } = this.xhr + if (status >= 200 && status < 300) { + this.attributes.signed_id = response.signed_blob_id + this.uploadURL = response.upload_to_url + this.callback(null, this.toJSON()) + } else { + this.requestDidError(event) + } + } + + requestDidError(event) { + this.callback(`Error creating Blob for "${this.file.name}". Status: ${this.xhr.status}`) + } + + toJSON() { + const result = {} + for (const key in this.attributes) { + result[key] = this.attributes[key] + } + return result + } +} diff --git a/app/javascript/activestorage/blob_upload.js b/app/javascript/activestorage/blob_upload.js new file mode 100644 index 0000000000..8c1335c56c --- /dev/null +++ b/app/javascript/activestorage/blob_upload.js @@ -0,0 +1,31 @@ +export class BlobUpload { + constructor(blob) { + this.blob = blob + this.file = blob.file + + this.xhr = new XMLHttpRequest + this.xhr.open("PUT", blob.uploadURL, true) + this.xhr.setRequestHeader("Content-Type", blob.attributes.content_type) + this.xhr.setRequestHeader("Content-MD5", blob.attributes.checksum) + this.xhr.addEventListener("load", event => this.requestDidLoad(event)) + this.xhr.addEventListener("error", event => this.requestDidError(event)) + } + + create(callback) { + this.callback = callback + this.xhr.send(this.file) + } + + requestDidLoad(event) { + const { status, response } = this.xhr + if (status >= 200 && status < 300) { + this.callback(null, this.xhr.response) + } else { + this.requestDidError(event) + } + } + + requestDidError(event) { + this.callback(`Error storing "${this.file.name}". Status: ${this.xhr.status}`) + } +} diff --git a/app/javascript/activestorage/direct_upload.js b/app/javascript/activestorage/direct_upload.js new file mode 100644 index 0000000000..7bbe4e0fdd --- /dev/null +++ b/app/javascript/activestorage/direct_upload.js @@ -0,0 +1,43 @@ +import { FileChecksum } from "./file_checksum" +import { BlobRecord } from "./blob_record" +import { BlobUpload } from "./blob_upload" + +let id = 0 + +export class DirectUpload { + constructor(file, options = {}) { + this.id = id++ + this.file = file + this.url = options.url + this.delegate = options.delegate + } + + create(callback) { + const fileChecksum = new FileChecksum(this.file) + fileChecksum.create((error, checksum) => { + const blob = new BlobRecord(this.file, checksum, this.url) + notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr) + blob.create(error => { + if (error) { + callback(error) + } else { + const upload = new BlobUpload(blob) + notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr) + upload.create(error => { + if (error) { + callback(error) + } else { + callback(null, blob.toJSON()) + } + }) + } + }) + }) + } +} + +function notify(object, methodName, ...messages) { + if (object && typeof object[methodName] == "function") { + return object[methodName](...messages) + } +} diff --git a/app/javascript/activestorage/direct_upload_controller.js b/app/javascript/activestorage/direct_upload_controller.js new file mode 100644 index 0000000000..a5541c81be --- /dev/null +++ b/app/javascript/activestorage/direct_upload_controller.js @@ -0,0 +1,67 @@ +import { DirectUpload } from "./direct_upload" +import { dispatchEvent } from "./helpers" + +export class DirectUploadController { + constructor(input, file) { + this.input = input + this.file = file + this.directUpload = new DirectUpload(this.file, { url: this.url, delegate: this }) + this.dispatch("initialize") + } + + start(callback) { + const hiddenInput = document.createElement("input") + hiddenInput.type = "hidden" + hiddenInput.name = this.input.name + this.input.insertAdjacentElement("beforebegin", hiddenInput) + + this.dispatch("start") + + this.directUpload.create((error, attributes) => { + if (error) { + hiddenInput.parentNode.removeChild(hiddenInput) + this.dispatchError(error) + } else { + hiddenInput.value = attributes.signed_id + } + + this.dispatch("end") + callback(error) + }) + } + + uploadRequestDidProgress(event) { + const progress = event.loaded / event.total * 100 + if (progress) { + this.dispatch("progress", { progress }) + } + } + + get url() { + return this.input.getAttribute("data-direct-upload-url") + } + + dispatch(name, detail = {}) { + detail.file = this.file + detail.id = this.directUpload.id + return dispatchEvent(this.input, `direct-upload:${name}`, { detail }) + } + + dispatchError(error) { + const event = this.dispatch("error", { error }) + if (!event.defaultPrevented) { + alert(error) + } + } + + // DirectUpload delegate + + directUploadWillCreateBlobWithXHR(xhr) { + this.dispatch("before-blob-request", { xhr }) + } + + directUploadWillStoreFileWithXHR(xhr) { + this.dispatch("before-storage-request", { xhr }) + xhr.upload.addEventListener("progress", event => this.uploadRequestDidProgress(event)) + } +} diff --git a/app/javascript/activestorage/direct_uploads_controller.js b/app/javascript/activestorage/direct_uploads_controller.js new file mode 100644 index 0000000000..94b89c9119 --- /dev/null +++ b/app/javascript/activestorage/direct_uploads_controller.js @@ -0,0 +1,50 @@ +import { DirectUploadController } from "./direct_upload_controller" +import { findElements, dispatchEvent, toArray } from "./helpers" + +const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])" + +export class DirectUploadsController { + constructor(form) { + this.form = form + this.inputs = findElements(form, inputSelector).filter(input => input.files.length) + } + + start(callback) { + const controllers = this.createDirectUploadControllers() + + const startNextController = () => { + const controller = controllers.shift() + if (controller) { + controller.start(error => { + if (error) { + callback(error) + this.dispatch("end") + } else { + startNextController() + } + }) + } else { + callback() + this.dispatch("end") + } + } + + this.dispatch("start") + startNextController() + } + + createDirectUploadControllers() { + const controllers = [] + this.inputs.forEach(input => { + toArray(input.files).forEach(file => { + const controller = new DirectUploadController(input, file) + controllers.push(controller) + }) + }) + return controllers + } + + dispatch(name, detail = {}) { + return dispatchEvent(this.form, `direct-uploads:${name}`, { detail }) + } +} diff --git a/app/javascript/activestorage/file_checksum.js b/app/javascript/activestorage/file_checksum.js new file mode 100644 index 0000000000..d7a10b3e55 --- /dev/null +++ b/app/javascript/activestorage/file_checksum.js @@ -0,0 +1,48 @@ +import SparkMD5 from "spark-md5" + +const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice + +export class FileChecksum { + constructor(file) { + this.file = file + this.chunkSize = 2097152 // 2MB + this.chunkCount = Math.ceil(this.file.size / this.chunkSize) + this.chunkIndex = 0 + } + + create(callback) { + this.callback = callback + this.md5Buffer = new SparkMD5.ArrayBuffer + this.fileReader = new FileReader + this.fileReader.addEventListener("load", event => this.fileReaderDidLoad(event)) + this.fileReader.addEventListener("error", event => this.fileReaderDidError(event)) + this.readNextChunk() + } + + fileReaderDidLoad(event) { + this.md5Buffer.append(event.target.result) + + if (!this.readNextChunk()) { + const binaryDigest = this.md5Buffer.end(true) + const base64digest = btoa(binaryDigest) + this.callback(null, base64digest) + } + } + + fileReaderDidError(event) { + this.callback(`Error reading ${this.file.name}`) + } + + readNextChunk() { + if (this.chunkIndex < this.chunkCount) { + const start = this.chunkIndex * this.chunkSize + const end = Math.min(start + this.chunkSize, this.file.size) + const bytes = fileSlice.call(this.file, start, end) + this.fileReader.readAsArrayBuffer(bytes) + this.chunkIndex++ + return true + } else { + return false + } + } +} diff --git a/app/javascript/activestorage/helpers.js b/app/javascript/activestorage/helpers.js new file mode 100644 index 0000000000..52fec8f6f1 --- /dev/null +++ b/app/javascript/activestorage/helpers.js @@ -0,0 +1,42 @@ +export function getMetaValue(name) { + const element = findElement(document.head, `meta[name="${name}"]`) + if (element) { + return element.getAttribute("content") + } +} + +export function findElements(root, selector) { + if (typeof root == "string") { + selector = root + root = document + } + const elements = root.querySelectorAll(selector) + return toArray(elements) +} + +export function findElement(root, selector) { + if (typeof root == "string") { + selector = root + root = document + } + return root.querySelector(selector) +} + +export function dispatchEvent(element, type, eventInit = {}) { + const { bubbles, cancelable, detail } = eventInit + const event = document.createEvent("Event") + event.initEvent(type, bubbles || true, cancelable || true) + event.detail = detail || {} + element.dispatchEvent(event) + return event +} + +export function toArray(value) { + if (Array.isArray(value)) { + return value + } else if (Array.from) { + return Array.from(value) + } else { + return [].slice.call(value) + } +} diff --git a/app/javascript/activestorage/index.js b/app/javascript/activestorage/index.js new file mode 100644 index 0000000000..a340008fb9 --- /dev/null +++ b/app/javascript/activestorage/index.js @@ -0,0 +1,11 @@ +import { start } from "./ujs" +import { DirectUpload } from "./direct_upload" +export { start, DirectUpload } + +function autostart() { + if (window.ActiveStorage) { + start() + } +} + +setTimeout(autostart, 1) diff --git a/app/javascript/activestorage/ujs.js b/app/javascript/activestorage/ujs.js new file mode 100644 index 0000000000..a2ce2cfc58 --- /dev/null +++ b/app/javascript/activestorage/ujs.js @@ -0,0 +1,74 @@ +import { DirectUploadsController } from "./direct_uploads_controller" +import { findElement } from "./helpers" + +const processingAttribute = "data-direct-uploads-processing" +let started = false + +export function start() { + if (!started) { + started = true + document.addEventListener("submit", didSubmitForm) + document.addEventListener("ajax:before", didSubmitRemoteElement) + } +} + +function didSubmitForm(event) { + handleFormSubmissionEvent(event) +} + +function didSubmitRemoteElement(event) { + if (event.target.tagName == "FORM") { + handleFormSubmissionEvent(event) + } +} + +function handleFormSubmissionEvent(event) { + const form = event.target + + if (form.hasAttribute(processingAttribute)) { + event.preventDefault() + return + } + + const controller = new DirectUploadsController(form) + const { inputs } = controller + + if (inputs.length) { + event.preventDefault() + form.setAttribute(processingAttribute, "") + inputs.forEach(disable) + controller.start(error => { + form.removeAttribute(processingAttribute) + if (error) { + inputs.forEach(enable) + } else { + submitForm(form) + } + }) + } +} + +function submitForm(form) { + let button = findElement(form, "input[type=submit]") + if (button) { + const { disabled } = button + button.disabled = false + button.click() + button.disabled = disabled + } else { + button = document.createElement("input") + button.type = "submit" + button.style = "display:none" + form.appendChild(button) + button.click() + form.removeChild(button) + } +} + +function disable(input) { + input.disabled = true +} + +function enable(input) { + input.disabled = false +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..ea2a38aee7 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "activestorage", + "version": "0.1.0", + "description": "Attach cloud and local files in Rails applications", + "main": "app/assets/javascripts/activestorage.js", + "repository": "git+https://github.com/rails/activestorage.git", + "author": "Javan Makhmali ", + "license": "MIT", + "devDependencies": { + "babel-core": "^6.25.0", + "babel-loader": "^7.1.1", + "babel-preset-env": "^1.6.0", + "spark-md5": "^3.0.0", + "webpack": "^3.4.0" + }, + "files": [ + "app/assets/javascripts/activestorage.js" + ], + "scripts": { + "build": "webpack -p" + } +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000000..92c4530e7f --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,27 @@ +const webpack = require("webpack") +const path = require("path") + +module.exports = { + entry: { + "activestorage": path.resolve(__dirname, "app/javascript/activestorage/index.js"), + }, + + output: { + filename: "[name].js", + path: path.resolve(__dirname, "app/assets/javascripts"), + library: "ActiveStorage", + libraryTarget: "umd" + }, + + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: "babel-loader" + } + } + ] + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000000..fd55f99ec1 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2627 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +abbrev@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" + +acorn-dynamic-import@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" + dependencies: + acorn "^4.0.3" + +acorn@^4.0.3: + version "4.0.13" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + +acorn@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75" + +ajv-keywords@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" + +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +ajv@^5.1.5: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +anymatch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + dependencies: + arrify "^1.0.0" + micromatch "^2.1.5" + +aproba@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-flatten@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asn1.js@^4.0.0: + version "4.9.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.1.tgz#48ba240b45a9280e94748990ba597d216617fd40" + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +assert@^1.1.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" + dependencies: + util "0.10.3" + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + +async@^2.1.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" + dependencies: + lodash "^4.14.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws4@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-code-frame@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" + dependencies: + chalk "^1.1.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +babel-core@^6.24.1, babel-core@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729" + dependencies: + babel-code-frame "^6.22.0" + babel-generator "^6.25.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.25.0" + babel-traverse "^6.25.0" + babel-types "^6.25.0" + babylon "^6.17.2" + convert-source-map "^1.1.0" + debug "^2.1.1" + json5 "^0.5.0" + lodash "^4.2.0" + minimatch "^3.0.2" + path-is-absolute "^1.0.0" + private "^0.1.6" + slash "^1.0.0" + source-map "^0.5.0" + +babel-generator@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-types "^6.25.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.2.0" + source-map "^0.5.0" + trim-right "^1.0.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-define-map@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz#7a9747f258d8947d32d515f6aa1c7bd02204a080" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + lodash "^4.2.0" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-optimise-call-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz#d36e22fab1008d79d88648e32116868128456ce8" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + lodash "^4.2.0" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-replace-supers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + dependencies: + babel-helper-optimise-call-expression "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-loader@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.1.tgz#b87134c8b12e3e4c2a94e0546085bc680a2b8488" + dependencies: + find-cache-dir "^1.0.0" + loader-utils "^1.0.2" + mkdirp "^0.5.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + +babel-plugin-syntax-trailing-function-commas@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-async-to-generator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoping@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz#76c295dc3a4741b1665adfd3167215dcff32a576" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + lodash "^4.2.0" + +babel-plugin-transform-es2015-classes@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + dependencies: + babel-helper-define-map "^6.24.1" + babel-helper-function-name "^6.24.1" + babel-helper-optimise-call-expression "^6.24.1" + babel-helper-replace-supers "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-computed-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-destructuring@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-duplicate-keys@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-for-of@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + dependencies: + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz#d3e310b40ef664a36622200097c6d440298f2bfe" + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-modules-systemjs@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-umd@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + dependencies: + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-object-super@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + dependencies: + babel-helper-replace-supers "^6.24.1" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-parameters@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-shorthand-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-template-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-typeof-symbol@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz#b8da305ad43c3c99b4848e4fe4037b770d23c418" + dependencies: + regenerator-transform "0.9.11" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-preset-env@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.0.tgz#2de1c782a780a0a5d605d199c957596da43c44e4" + dependencies: + babel-plugin-check-es2015-constants "^6.22.0" + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-to-generator "^6.22.0" + babel-plugin-transform-es2015-arrow-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoping "^6.23.0" + babel-plugin-transform-es2015-classes "^6.23.0" + babel-plugin-transform-es2015-computed-properties "^6.22.0" + babel-plugin-transform-es2015-destructuring "^6.23.0" + babel-plugin-transform-es2015-duplicate-keys "^6.22.0" + babel-plugin-transform-es2015-for-of "^6.23.0" + babel-plugin-transform-es2015-function-name "^6.22.0" + babel-plugin-transform-es2015-literals "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.22.0" + babel-plugin-transform-es2015-modules-commonjs "^6.23.0" + babel-plugin-transform-es2015-modules-systemjs "^6.23.0" + babel-plugin-transform-es2015-modules-umd "^6.23.0" + babel-plugin-transform-es2015-object-super "^6.22.0" + babel-plugin-transform-es2015-parameters "^6.23.0" + babel-plugin-transform-es2015-shorthand-properties "^6.22.0" + babel-plugin-transform-es2015-spread "^6.22.0" + babel-plugin-transform-es2015-sticky-regex "^6.22.0" + babel-plugin-transform-es2015-template-literals "^6.22.0" + babel-plugin-transform-es2015-typeof-symbol "^6.23.0" + babel-plugin-transform-es2015-unicode-regex "^6.22.0" + babel-plugin-transform-exponentiation-operator "^6.22.0" + babel-plugin-transform-regenerator "^6.22.0" + browserslist "^2.1.2" + invariant "^2.2.2" + semver "^5.3.0" + +babel-register@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f" + dependencies: + babel-core "^6.24.1" + babel-runtime "^6.22.0" + core-js "^2.4.0" + home-or-tmp "^2.0.0" + lodash "^4.2.0" + mkdirp "^0.5.1" + source-map-support "^0.4.2" + +babel-runtime@^6.18.0, babel-runtime@^6.22.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-template@^6.24.1, babel-template@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.25.0" + babel-types "^6.25.0" + babylon "^6.17.2" + lodash "^4.2.0" + +babel-traverse@^6.24.1, babel-traverse@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1" + dependencies: + babel-code-frame "^6.22.0" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-types "^6.25.0" + babylon "^6.17.2" + debug "^2.2.0" + globals "^9.0.0" + invariant "^2.2.0" + lodash "^4.2.0" + +babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e" + dependencies: + babel-runtime "^6.22.0" + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^1.0.1" + +babylon@^6.17.2: + version "6.17.4" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base64-js@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +big.js@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978" + +binary-extensions@^1.0.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.9.0.tgz#66506c16ce6f4d6928a5b3cd6a33ca41e941e37b" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.7" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a" + dependencies: + buffer-xor "^1.0.2" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + inherits "^2.0.1" + +browserify-cipher@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a" + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd" + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" + dependencies: + pako "~0.2.0" + +browserslist@^2.1.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.2.2.tgz#e9b4618b8a01c193f9786beea09f6fd10dbe31c3" + dependencies: + caniuse-lite "^1.0.30000704" + electron-to-chromium "^1.3.16" + +buffer-xor@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + +buffer@^4.3.0: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + +caniuse-lite@^1.0.30000704: + version "1.0.30000706" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000706.tgz#bc59abc41ba7d4a3634dda95befded6114e1f24e" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chalk@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chokidar@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + dependencies: + delayed-stream "~1.0.0" + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +console-browserify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" + dependencies: + date-now "^0.1.4" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + +convert-source-map@^1.1.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" + +core-js@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +create-ecdh@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + ripemd160 "^2.0.0" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.6" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +crypto-browserify@^3.11.0: + version "3.11.1" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f" + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + dependencies: + es5-ext "^0.10.9" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +date-now@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" + +debug@^2.1.1, debug@^2.2.0: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0, decamelize@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +des.js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +diffie-hellman@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +domain-browser@^1.1.1: + version "1.1.7" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +electron-to-chromium@^1.3.16: + version "1.3.16" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.16.tgz#d0e026735754770901ae301a21664cba45d92f7d" + +elliptic@^6.0.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + +enhanced-resolve@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + object-assign "^4.0.1" + tapable "^0.2.7" + +errno@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" + dependencies: + prr "~0.0.0" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.24" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14" + dependencies: + es6-iterator "2" + es6-symbol "~3.1" + +es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-symbol "^3.1" + +es6-map@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" + +es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" + +es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-weak-map@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + +escape-string-regexp@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escope@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" + dependencies: + es6-map "^0.1.3" + es6-weak-map "^2.0.1" + esrecurse "^4.1.0" + estraverse "^4.1.1" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^4.1.0, estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +event-emitter@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + dependencies: + d "1" + es5-ext "~0.10.14" + +events@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + +evp_bytestokey@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz#497b66ad9fef65cd7c08a6180824ba1476b66e53" + dependencies: + create-hash "^1.1.1" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +extend@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extsprintf@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" + +fast-deep-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +find-cache-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" + dependencies: + commondir "^1.0.1" + make-dir "^1.0.0" + pkg-dir "^2.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.36" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-caller-file@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob@^7.0.5: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^9.0.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +hash-base@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" + dependencies: + inherits "^2.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.0" + +hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +hosted-git-info@^2.1.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" + +ieee754@^1.1.4: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + +ini@~1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" + +interpret@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" + +invariant@^2.2.0, invariant@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +js-tokens@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-loader@^0.5.4: + version "0.5.7" + resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json5@^0.5.0, json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsprim@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" + dependencies: + assert-plus "1.0.0" + extsprintf "1.0.2" + json-schema "0.2.3" + verror "1.3.6" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +loader-runner@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" + +loader-utils@^1.0.2, loader-utils@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash@^4.14.0, lodash@^4.2.0: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +make-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" + dependencies: + pify "^2.3.0" + +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + +memory-fs@^0.4.0, memory-fs@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +micromatch@^2.1.5: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +miller-rabin@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.0.tgz#4a62fb1d42933c05583982f4c716f6fb9e6c6d3d" + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@~1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" + +mime-types@^2.1.12, mime-types@~2.1.7: + version "2.1.16" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23" + dependencies: + mime-db "~1.29.0" + +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +minimalistic-assert@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +nan@^2.3.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" + +node-libs-browser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646" + dependencies: + assert "^1.1.1" + browserify-zlib "^0.1.4" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^1.0.0" + https-browserify "0.0.1" + os-browserify "^0.2.0" + path-browserify "0.0.0" + process "^0.11.0" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.0.5" + stream-browserify "^2.0.1" + stream-http "^2.3.1" + string_decoder "^0.10.25" + timers-browserify "^2.0.2" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.10.3" + vm-browserify "0.0.4" + +node-pre-gyp@^0.6.36: + version "0.6.36" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" + dependencies: + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "^2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +oauth-sign@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +once@^1.3.0, once@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +os-browserify@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +pako@~0.2.0: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + +parse-asn1@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +path-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +pbkdf2@^3.0.3: + version "3.0.12" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.12.tgz#be36785c5067ea48d806ff923288c5f750b6b8a2" + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +private@^0.1.6: + version "0.1.7" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +process@^0.11.0: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + +prr@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +public-encrypt@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + +punycode@^1.2.4, punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +randombytes@^2.0.0, randombytes@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.5.tgz#dc009a246b8d09a177b4b7a0ae77bc570f4b1b79" + dependencies: + safe-buffer "^5.1.0" + +rc@^1.1.7: + version "1.2.1" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.6: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + +regenerate@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" + +regenerator-runtime@^0.10.0: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + +regenerator-transform@0.9.11: + version "0.9.11" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.11.tgz#3a7d067520cb7b7176769eb5ff868691befe1283" + dependencies: + babel-runtime "^6.18.0" + babel-types "^6.19.0" + private "^0.1.6" + +regex-cache@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" + dependencies: + is-equal-shallow "^0.1.3" + is-primitive "^2.0.0" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz#69b062d978727ad14dc6b56ba4ab772fd8d70511" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request@^2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@2, rimraf@^2.5.1, rimraf@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + dependencies: + glob "^7.0.5" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" + dependencies: + hash-base "^2.0.0" + inherits "^2.0.1" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.8" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f" + dependencies: + inherits "^2.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +source-list-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" + +source-map-support@^0.4.2: + version "0.4.15" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" + dependencies: + source-map "^0.5.6" + +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + +spark-md5@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.0.tgz#3722227c54e2faf24b1dc6d933cc144e6f71bfef" + +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +stream-browserify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-http@^2.3.1: + version "2.7.2" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad" + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.2.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@^0.10.25: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +stringstream@~0.0.4: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836" + dependencies: + has-flag "^2.0.0" + +tapable@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c" + +tar-pack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +timers-browserify@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86" + dependencies: + setimmediate "^1.0.4" + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + +to-fast-properties@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +tough-cookie@~2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" + dependencies: + punycode "^1.4.1" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +uglify-js@^2.8.29: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +uglifyjs-webpack-plugin@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" + dependencies: + source-map "^0.5.6" + uglify-js "^2.8.29" + webpack-sources "^1.0.1" + +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +util@0.10.3, util@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + +uuid@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + +verror@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" + dependencies: + extsprintf "1.0.2" + +vm-browserify@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" + dependencies: + indexof "0.0.1" + +watchpack@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" + dependencies: + async "^2.1.2" + chokidar "^1.7.0" + graceful-fs "^4.1.2" + +webpack-sources@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf" + dependencies: + source-list-map "^2.0.0" + source-map "~0.5.3" + +webpack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.0.tgz#e9465b660ad79dd2d33874d968b31746ea9a8e63" + dependencies: + acorn "^5.0.0" + acorn-dynamic-import "^2.0.0" + ajv "^5.1.5" + ajv-keywords "^2.0.0" + async "^2.1.2" + enhanced-resolve "^3.4.0" + escope "^3.6.0" + interpret "^1.0.0" + json-loader "^0.5.4" + json5 "^0.5.1" + loader-runner "^2.3.0" + loader-utils "^1.1.0" + memory-fs "~0.4.1" + mkdirp "~0.5.0" + node-libs-browser "^2.0.0" + source-map "^0.5.3" + supports-color "^4.2.1" + tapable "^0.2.7" + uglifyjs-webpack-plugin "^0.4.6" + watchpack "^1.4.0" + webpack-sources "^1.0.1" + yargs "^8.0.2" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + +which@^1.2.9: + version "1.2.14" + resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +xtend@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + dependencies: + camelcase "^4.1.0" + +yargs@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" From a0a23e9f5d88cf668e649500de594c3e506b0946 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Fri, 28 Jul 2017 11:17:48 -0400 Subject: [PATCH 279/289] Add JavaScript linting (#82) --- .eslintrc | 19 + .travis.yml | 14 +- app/assets/javascripts/activestorage.js | 2 +- app/javascript/activestorage/blob_upload.js | 2 +- package.json | 6 +- yarn.lock | 575 +++++++++++++++++++- 6 files changed, 594 insertions(+), 24 deletions(-) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..3d9ecd4bce --- /dev/null +++ b/.eslintrc @@ -0,0 +1,19 @@ +{ + "extends": "eslint:recommended", + "rules": { + "semi": ["error", "never"], + "quotes": ["error", "double"], + "no-unused-vars": ["error", { "vars": "all", "args": "none" }] + }, + "plugins": [ + "import" + ], + "env": { + "browser": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + } +} diff --git a/.travis.yml b/.travis.yml index 324890e2fb..89edc05c78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,14 +2,24 @@ language: ruby sudo: false bundler: true -script: bundle exec rake - rvm: - 2.3 - 2.4 - ruby-head +cache: + bundler: true + yarn: true + matrix: allow_failures: - rvm: ruby-head fast_finish: true + +install: + - bundle install + - yarn install + +script: + - bundle exec rake + - yarn lint diff --git a/app/assets/javascripts/activestorage.js b/app/assets/javascripts/activestorage.js index 61c8e4ce54..7c946384cd 100644 --- a/app/assets/javascripts/activestorage.js +++ b/app/assets/javascripts/activestorage.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.bubbles,i=r.cancelable,a=r.detail,u=document.createEvent("Event");return u.initEvent(e,n||!0,i||!0),u.detail=a||{},t.dispatchEvent(u),u}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i1&&void 0!==arguments[1]?arguments[1]:{};n(this,t),this.id=f++,this.file=e,this.url=r.url,this.delegate=r.delegate}return s(t,[{key:"create",value:function(t){var e=this;new a.a(this.file).create(function(r,n){var a=new u.a(e.file,n,e.url);i(e.delegate,"directUploadWillCreateBlobWithXHR",a.xhr),a.create(function(r){if(r)t(r);else{var n=new o.a(a);i(e.delegate,"directUploadWillStoreFileWithXHR",n.xhr),n.create(function(e){e?t(e):t(null,a.toJSON())})}})})}}]),t}()},function(t,e,r){"use strict";function n(){window.ActiveStorage&&Object(i.a)()}Object.defineProperty(e,"__esModule",{value:!0});var i=r(3),a=r(1);r.d(e,"start",function(){return i.a}),r.d(e,"DirectUpload",function(){return a.a}),setTimeout(n,1)},function(t,e,r){"use strict";function n(){d||(d=!0,document.addEventListener("submit",i),document.addEventListener("ajax:before",a))}function i(t){u(t)}function a(t){"FORM"==t.target.tagName&&u(t)}function u(t){var e=t.target;if(e.hasAttribute(l))return void t.preventDefault();var r=new c.a(e),n=r.inputs;n.length&&(t.preventDefault(),e.setAttribute(l,""),n.forEach(s),r.start(function(t){e.removeAttribute(l),t?n.forEach(f):o(e)}))}function o(t){var e=Object(h.b)(t,"input[type=submit]");if(e){var r=e,n=r.disabled;e.disabled=!1,e.click(),e.disabled=n}else e=document.createElement("input"),e.type="submit",e.style="display:none",t.appendChild(e),e.click(),t.removeChild(e)}function s(t){t.disabled=!0}function f(t){t.disabled=!1}e.a=n;var c=r(4),h=r(0),l="data-direct-uploads-processing",d=!1},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(5),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r=200&&r<300?(this.attributes.signed_id=n.signed_blob_id,this.uploadURL=n.upload_to_url,this.callback(null,this.toJSON())):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.xhr.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r=200&&r<300?this.callback(null,this.xhr.response):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])}); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.bubbles,i=r.cancelable,a=r.detail,u=document.createEvent("Event");return u.initEvent(e,n||!0,i||!0),u.detail=a||{},t.dispatchEvent(u),u}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i1&&void 0!==arguments[1]?arguments[1]:{};n(this,t),this.id=f++,this.file=e,this.url=r.url,this.delegate=r.delegate}return s(t,[{key:"create",value:function(t){var e=this;new a.a(this.file).create(function(r,n){var a=new u.a(e.file,n,e.url);i(e.delegate,"directUploadWillCreateBlobWithXHR",a.xhr),a.create(function(r){if(r)t(r);else{var n=new o.a(a);i(e.delegate,"directUploadWillStoreFileWithXHR",n.xhr),n.create(function(e){e?t(e):t(null,a.toJSON())})}})})}}]),t}()},function(t,e,r){"use strict";function n(){window.ActiveStorage&&Object(i.a)()}Object.defineProperty(e,"__esModule",{value:!0});var i=r(3),a=r(1);r.d(e,"start",function(){return i.a}),r.d(e,"DirectUpload",function(){return a.a}),setTimeout(n,1)},function(t,e,r){"use strict";function n(){d||(d=!0,document.addEventListener("submit",i),document.addEventListener("ajax:before",a))}function i(t){u(t)}function a(t){"FORM"==t.target.tagName&&u(t)}function u(t){var e=t.target;if(e.hasAttribute(l))return void t.preventDefault();var r=new c.a(e),n=r.inputs;n.length&&(t.preventDefault(),e.setAttribute(l,""),n.forEach(s),r.start(function(t){e.removeAttribute(l),t?n.forEach(f):o(e)}))}function o(t){var e=Object(h.b)(t,"input[type=submit]");if(e){var r=e,n=r.disabled;e.disabled=!1,e.click(),e.disabled=n}else e=document.createElement("input"),e.type="submit",e.style="display:none",t.appendChild(e),e.click(),t.removeChild(e)}function s(t){t.disabled=!0}function f(t){t.disabled=!1}e.a=n;var c=r(4),h=r(0),l="data-direct-uploads-processing",d=!1},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(5),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r=200&&r<300?(this.attributes.signed_id=n.signed_blob_id,this.uploadURL=n.upload_to_url,this.callback(null,this.toJSON())):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.xhr.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])}); \ No newline at end of file diff --git a/app/javascript/activestorage/blob_upload.js b/app/javascript/activestorage/blob_upload.js index 8c1335c56c..c72820b433 100644 --- a/app/javascript/activestorage/blob_upload.js +++ b/app/javascript/activestorage/blob_upload.js @@ -19,7 +19,7 @@ export class BlobUpload { requestDidLoad(event) { const { status, response } = this.xhr if (status >= 200 && status < 300) { - this.callback(null, this.xhr.response) + this.callback(null, response) } else { this.requestDidError(event) } diff --git a/package.json b/package.json index ea2a38aee7..362286e857 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "babel-core": "^6.25.0", "babel-loader": "^7.1.1", "babel-preset-env": "^1.6.0", + "eslint": "^4.3.0", + "eslint-plugin-import": "^2.7.0", "spark-md5": "^3.0.0", "webpack": "^3.4.0" }, @@ -17,6 +19,8 @@ "app/assets/javascripts/activestorage.js" ], "scripts": { - "build": "webpack -p" + "prebuild": "yarn lint", + "build": "webpack -p", + "lint": "eslint app/javascript" } } diff --git a/yarn.lock b/yarn.lock index fd55f99ec1..dd09577445 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,26 +12,40 @@ acorn-dynamic-import@^2.0.0: dependencies: acorn "^4.0.3" +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + acorn@^4.0.3: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" -acorn@^5.0.0: +acorn@^5.0.0, acorn@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75" +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + ajv-keywords@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" -ajv@^4.9.1: +ajv@^4.7.0, ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" dependencies: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.1.5: +ajv@^5.1.5, ajv@^5.2.0: version "5.2.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39" dependencies: @@ -48,6 +62,10 @@ align-text@^0.1.1, align-text@^0.1.3: longest "^1.0.1" repeat-string "^1.5.2" +ansi-escapes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -60,6 +78,12 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" +ansi-styles@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + anymatch@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" @@ -78,6 +102,12 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +argparse@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + dependencies: + sprintf-js "~1.0.2" + arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" @@ -88,6 +118,16 @@ arr-flatten@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" @@ -737,7 +777,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -builtin-modules@^1.0.0: +builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -745,6 +785,16 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + camelcase@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" @@ -768,7 +818,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@^1.1.0: +chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -778,6 +828,14 @@ chalk@^1.1.0: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + chokidar@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" @@ -800,6 +858,20 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -824,6 +896,16 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" +color-convert@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" @@ -838,6 +920,14 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" +concat-stream@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" @@ -852,6 +942,10 @@ constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + convert-source-map@^1.1.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -891,7 +985,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@^5.0.1: +cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" dependencies: @@ -936,7 +1030,7 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@^2.1.1, debug@^2.2.0: +debug@^2.1.1, debug@^2.2.0, debug@^2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: @@ -950,6 +1044,22 @@ deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -979,6 +1089,20 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" @@ -1082,7 +1206,7 @@ es6-weak-map@^2.0.1: es6-iterator "^2.0.1" es6-symbol "^3.1.1" -escape-string-regexp@^1.0.2: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1095,6 +1219,110 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-config-airbnb-base@^11.3.1: + version "11.3.1" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.3.1.tgz#c0ab108c9beed503cb999e4c60f4ef98eda0ed30" + dependencies: + eslint-restricted-globals "^0.1.1" + +eslint-import-resolver-node@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc" + dependencies: + debug "^2.6.8" + resolve "^1.2.0" + +eslint-module-utils@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-import@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz#21de33380b9efb55f5ef6d2e210ec0e07e7fa69f" + dependencies: + builtin-modules "^1.1.1" + contains-path "^0.1.0" + debug "^2.6.8" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.1.1" + has "^1.0.1" + lodash.cond "^4.3.0" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + +eslint-restricted-globals@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" + +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.3.0.tgz#fcd7c96376bbf34c85ee67ed0012a299642b108f" + dependencies: + ajv "^5.2.0" + babel-code-frame "^6.22.0" + chalk "^1.1.3" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^2.6.8" + doctrine "^2.0.0" + eslint-scope "^3.7.1" + espree "^3.4.3" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^9.17.0" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.8.4" + json-stable-stringify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^4.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-json-comments "~2.0.1" + table "^4.0.1" + text-table "~0.2.0" + +espree@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.3.tgz#2910b5ccd49ce893c2ffffaab4fd8b3a31b82374" + dependencies: + acorn "^5.0.1" + acorn-jsx "^3.0.0" + +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +esquery@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + dependencies: + estraverse "^4.0.0" + esrecurse@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" @@ -1102,7 +1330,7 @@ esrecurse@^4.1.0: estraverse "^4.1.0" object-assign "^4.0.1" -estraverse@^4.1.0, estraverse@^4.1.1: +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" @@ -1155,6 +1383,14 @@ extend@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +external-editor@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972" + dependencies: + iconv-lite "^0.4.17" + jschardet "^1.4.2" + tmp "^0.0.31" + extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -1169,6 +1405,23 @@ fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -1191,12 +1444,28 @@ find-cache-dir@^1.0.0: make-dir "^1.0.0" pkg-dir "^2.0.0" +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" dependencies: locate-path "^2.0.0" +flat-cache@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -1247,6 +1516,14 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +function-bind@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -1287,7 +1564,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@^7.0.5: +glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -1298,10 +1575,21 @@ glob@^7.0.5: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^9.0.0: +globals@^9.0.0, globals@^9.17.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -1331,6 +1619,12 @@ has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + hash-base@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" @@ -1388,10 +1682,22 @@ https-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" +iconv-lite@^0.4.17: + version "0.4.18" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" + ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" +ignore@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" @@ -1415,6 +1721,25 @@ ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" +inquirer@^3.0.6: + version "3.2.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.2.1.tgz#06ceb0f540f45ca548c17d6840959878265fa175" + dependencies: + ansi-escapes "^2.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + interpret@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" @@ -1501,6 +1826,22 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" @@ -1509,6 +1850,16 @@ is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -1539,10 +1890,21 @@ js-tokens@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" +js-yaml@^3.8.4: + version "3.9.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.0.tgz#4ffbbf25c2ac963b8299dc74da7e3740de1c18ce" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" +jschardet@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.0.tgz#a61f310306a5a71188e1b1acd08add3cfbb08b1e" + jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -1612,6 +1974,13 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + load-json-file@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" @@ -1640,7 +2009,11 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash@^4.14.0, lodash@^4.2.0: +lodash.cond@^4.3.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" + +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -1727,7 +2100,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" -minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -1751,10 +2124,18 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + nan@^2.3.0: version "2.6.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + node-libs-browser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646" @@ -1859,6 +2240,23 @@ once@^1.3.0, once@^1.3.3: dependencies: wrappy "1" +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + os-browserify@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" @@ -1875,7 +2273,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -1933,6 +2331,12 @@ path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -1941,10 +2345,18 @@ path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + path-key@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -1969,12 +2381,36 @@ pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" dependencies: find-up "^2.1.0" +pluralize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" @@ -1991,6 +2427,10 @@ process@^0.11.0: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + prr@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" @@ -2066,7 +2506,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.6: +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -2181,13 +2621,37 @@ require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" + dependencies: + path-parse "^1.0.5" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.5.1, rimraf@^2.6.1: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" dependencies: @@ -2200,6 +2664,22 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^2.0.0" inherits "^2.0.1" +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -2236,7 +2716,7 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" -signal-exit@^3.0.0: +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -2244,6 +2724,10 @@ slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" @@ -2282,6 +2766,10 @@ spdx-license-ids@^1.0.2: version "1.2.2" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + sshpk@^1.7.0: version "1.13.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" @@ -2321,7 +2809,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0: +string-width@^2.0.0, string-width@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: @@ -2370,12 +2858,23 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^4.2.1: +supports-color@^4.0.0, supports-color@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836" dependencies: has-flag "^2.0.0" +table@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + tapable@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c" @@ -2401,12 +2900,26 @@ tar@^2.2.1: fstream "^1.0.2" inherits "2" +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + timers-browserify@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86" dependencies: setimmediate "^1.0.4" +tmp@^0.0.31: + version "0.0.31" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" + dependencies: + os-tmpdir "~1.0.1" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -2425,6 +2938,10 @@ trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -2439,6 +2956,16 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" @@ -2570,6 +3097,10 @@ wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -2581,6 +3112,12 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" From b0b072ff2e67f2ef0954845c66bfa2008cd0fd7d Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Fri, 28 Jul 2017 11:46:38 -0400 Subject: [PATCH 280/289] Minor constructor signature tweaks --- app/assets/javascripts/activestorage.js | 2 +- app/javascript/activestorage/direct_upload.js | 11 +++++------ .../activestorage/direct_upload_controller.js | 2 +- app/javascript/activestorage/file_checksum.js | 5 +++++ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/activestorage.js b/app/assets/javascripts/activestorage.js index 7c946384cd..8efea079e9 100644 --- a/app/assets/javascripts/activestorage.js +++ b/app/assets/javascripts/activestorage.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.bubbles,i=r.cancelable,a=r.detail,u=document.createEvent("Event");return u.initEvent(e,n||!0,i||!0),u.detail=a||{},t.dispatchEvent(u),u}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i1&&void 0!==arguments[1]?arguments[1]:{};n(this,t),this.id=f++,this.file=e,this.url=r.url,this.delegate=r.delegate}return s(t,[{key:"create",value:function(t){var e=this;new a.a(this.file).create(function(r,n){var a=new u.a(e.file,n,e.url);i(e.delegate,"directUploadWillCreateBlobWithXHR",a.xhr),a.create(function(r){if(r)t(r);else{var n=new o.a(a);i(e.delegate,"directUploadWillStoreFileWithXHR",n.xhr),n.create(function(e){e?t(e):t(null,a.toJSON())})}})})}}]),t}()},function(t,e,r){"use strict";function n(){window.ActiveStorage&&Object(i.a)()}Object.defineProperty(e,"__esModule",{value:!0});var i=r(3),a=r(1);r.d(e,"start",function(){return i.a}),r.d(e,"DirectUpload",function(){return a.a}),setTimeout(n,1)},function(t,e,r){"use strict";function n(){d||(d=!0,document.addEventListener("submit",i),document.addEventListener("ajax:before",a))}function i(t){u(t)}function a(t){"FORM"==t.target.tagName&&u(t)}function u(t){var e=t.target;if(e.hasAttribute(l))return void t.preventDefault();var r=new c.a(e),n=r.inputs;n.length&&(t.preventDefault(),e.setAttribute(l,""),n.forEach(s),r.start(function(t){e.removeAttribute(l),t?n.forEach(f):o(e)}))}function o(t){var e=Object(h.b)(t,"input[type=submit]");if(e){var r=e,n=r.disabled;e.disabled=!1,e.click(),e.disabled=n}else e=document.createElement("input"),e.type="submit",e.style="display:none",t.appendChild(e),e.click(),t.removeChild(e)}function s(t){t.disabled=!0}function f(t){t.disabled=!1}e.a=n;var c=r(4),h=r(0),l="data-direct-uploads-processing",d=!1},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(5),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r=200&&r<300?(this.attributes.signed_id=n.signed_blob_id,this.uploadURL=n.upload_to_url,this.callback(null,this.toJSON())):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.xhr.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])}); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.bubbles,i=r.cancelable,a=r.detail,u=document.createEvent("Event");return u.initEvent(e,n||!0,i||!0),u.detail=a||{},t.dispatchEvent(u),u}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r=200&&r<300?(this.attributes.signed_id=n.signed_blob_id,this.uploadURL=n.upload_to_url,this.callback(null,this.toJSON())):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.xhr.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])}); \ No newline at end of file diff --git a/app/javascript/activestorage/direct_upload.js b/app/javascript/activestorage/direct_upload.js index 7bbe4e0fdd..7085e0a4ab 100644 --- a/app/javascript/activestorage/direct_upload.js +++ b/app/javascript/activestorage/direct_upload.js @@ -5,16 +5,15 @@ import { BlobUpload } from "./blob_upload" let id = 0 export class DirectUpload { - constructor(file, options = {}) { - this.id = id++ + constructor(file, url, delegate) { + this.id = ++id this.file = file - this.url = options.url - this.delegate = options.delegate + this.url = url + this.delegate = delegate } create(callback) { - const fileChecksum = new FileChecksum(this.file) - fileChecksum.create((error, checksum) => { + FileChecksum.create(this.file, (error, checksum) => { const blob = new BlobRecord(this.file, checksum, this.url) notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr) blob.create(error => { diff --git a/app/javascript/activestorage/direct_upload_controller.js b/app/javascript/activestorage/direct_upload_controller.js index a5541c81be..987050889a 100644 --- a/app/javascript/activestorage/direct_upload_controller.js +++ b/app/javascript/activestorage/direct_upload_controller.js @@ -5,7 +5,7 @@ export class DirectUploadController { constructor(input, file) { this.input = input this.file = file - this.directUpload = new DirectUpload(this.file, { url: this.url, delegate: this }) + this.directUpload = new DirectUpload(this.file, this.url, this) this.dispatch("initialize") } diff --git a/app/javascript/activestorage/file_checksum.js b/app/javascript/activestorage/file_checksum.js index d7a10b3e55..ffaec1a128 100644 --- a/app/javascript/activestorage/file_checksum.js +++ b/app/javascript/activestorage/file_checksum.js @@ -3,6 +3,11 @@ import SparkMD5 from "spark-md5" const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice export class FileChecksum { + static create(file, callback) { + const instance = new FileChecksum(file) + instance.create(callback) + } + constructor(file) { this.file = file this.chunkSize = 2097152 // 2MB From fd438769cc90882809de4ee469a1a999e8c026e6 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Fri, 28 Jul 2017 11:54:57 -0400 Subject: [PATCH 281/289] Add npm package details --- package.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 362286e857..57d62ecc5c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,17 @@ "version": "0.1.0", "description": "Attach cloud and local files in Rails applications", "main": "app/assets/javascripts/activestorage.js", - "repository": "git+https://github.com/rails/activestorage.git", + "files": [ + "app/assets/javascripts/*.js" + ], + "homepage": "https://github.com/rails/activestorage", + "repository": { + "type": "git", + "url": "git+https://github.com/rails/activestorage.git" + }, + "bugs": { + "url": "https://github.com/rails/activestorage/issues" + }, "author": "Javan Makhmali ", "license": "MIT", "devDependencies": { @@ -15,9 +25,6 @@ "spark-md5": "^3.0.0", "webpack": "^3.4.0" }, - "files": [ - "app/assets/javascripts/activestorage.js" - ], "scripts": { "prebuild": "yarn lint", "build": "webpack -p", From 696b8c6f43214ed4b9b79f36a9f68de0db19f1ee Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Jul 2017 15:13:22 -0500 Subject: [PATCH 282/289] Add a direct_upload: true option to file_field_tag and Form#file_field Then users don't have to bother with manually referring to internal routes. --- README.md | 2 +- .../file_field_with_direct_upload_helper.rb | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 app/helpers/active_storage/file_field_with_direct_upload_helper.rb diff --git a/README.md b/README.md index b127f87c5c..4a5f03233d 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Active Storage, with its included JavaScript library, supports uploading directl 2. Annotate file inputs with the direct upload URL. ```ruby - <%= form.file_field :attachments, multiple: true, data: { direct_upload_url: rails_direct_uploads_url } %> + <%= form.file_field :attachments, multiple: true, direct_upload: true %> ``` 3. That's it! Uploads begin upon form submission. diff --git a/app/helpers/active_storage/file_field_with_direct_upload_helper.rb b/app/helpers/active_storage/file_field_with_direct_upload_helper.rb new file mode 100644 index 0000000000..87af79cdfd --- /dev/null +++ b/app/helpers/active_storage/file_field_with_direct_upload_helper.rb @@ -0,0 +1,18 @@ +module ActiveStorage + # Temporary hack to overwrite the default file_field_tag and Form#file_field to accept a direct_upload: true option + # that then gets replaced with a data-direct-upload-url attribute with the route prefilled. + module FileFieldWithDirectUploadHelper + def file_field_tag(name, options = {}) + text_field_tag(name, nil, convert_direct_upload_option_to_url(options.merge(type: :file))) + end + + def file_field(object_name, method, options = {}) + ActionView::Helpers::Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options)).render + end + + private + def convert_direct_upload_option_to_url(options) + options.merge('data-direct-upload-url': rails_direct_uploads_url) if options.delete(:direct_upload) + end + end +end From 801b4eb465cd48435abddac881b92c93470b6933 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Jul 2017 15:27:43 -0500 Subject: [PATCH 283/289] Add Blob#type as a StringInquirer --- app/models/active_storage/blob.rb | 14 ++++++++++++++ test/models/blob_test.rb | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index 6a7836b9e5..b2d5b2362c 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -84,6 +84,20 @@ def filename ActiveStorage::Filename.new(self[:filename]) end + # Returns a `StringInquirer` based on the content_type that is broken into text, image, audio, video, pdf, or, + # the catch-all, file. Example: `messages.attachments.select(&:image?)`. + def type + @type ||= + case content_type + when /^text/ then 'text' + when /^image/ then 'image' + when /^audio/ then 'audio' + when /^video/ then 'video' + when /pdf/ then 'pdf' + else 'file' + end.inquiry + end + # Returns a `ActiveStorage::Variant` instance with the set of `transformations` passed in. This is only relevant # for image files, and it allows any image to be transformed for size, colors, and the like. Example: # diff --git a/test/models/blob_test.rb b/test/models/blob_test.rb index a5b291d5db..b51be7a93b 100644 --- a/test/models/blob_test.rb +++ b/test/models/blob_test.rb @@ -11,6 +11,12 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase assert_equal Digest::MD5.base64digest(data), blob.checksum end + test "inquery type" do + blob = create_blob data: "Hello world!" + assert blob.type.text? + assert_not blob.type.audio? + end + test "download yields chunks" do blob = create_blob data: "a" * 75.kilobytes chunks = [] From 1f150e0218918ae6a9f82dbb89621c16a5c7ddc2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Jul 2017 15:43:53 -0500 Subject: [PATCH 284/289] Must always return the options even if they werent converted --- .../active_storage/file_field_with_direct_upload_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/active_storage/file_field_with_direct_upload_helper.rb b/app/helpers/active_storage/file_field_with_direct_upload_helper.rb index 87af79cdfd..7c5fd0eb55 100644 --- a/app/helpers/active_storage/file_field_with_direct_upload_helper.rb +++ b/app/helpers/active_storage/file_field_with_direct_upload_helper.rb @@ -12,7 +12,7 @@ def file_field(object_name, method, options = {}) private def convert_direct_upload_option_to_url(options) - options.merge('data-direct-upload-url': rails_direct_uploads_url) if options.delete(:direct_upload) + options.merge('data-direct-upload-url': options.delete(:direct_upload) ? rails_direct_uploads_url : nil).compact end end end From a91bb13b8d5b7e3a75feed23e76121c516116d35 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Jul 2017 15:49:43 -0500 Subject: [PATCH 285/289] Convert type inquiry into root predicates for base types alone instead --- app/models/active_storage/blob.rb | 24 +++++++++++------------- test/models/blob_test.rb | 6 +++--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index b2d5b2362c..0f9562e23b 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -84,19 +84,17 @@ def filename ActiveStorage::Filename.new(self[:filename]) end - # Returns a `StringInquirer` based on the content_type that is broken into text, image, audio, video, pdf, or, - # the catch-all, file. Example: `messages.attachments.select(&:image?)`. - def type - @type ||= - case content_type - when /^text/ then 'text' - when /^image/ then 'image' - when /^audio/ then 'audio' - when /^video/ then 'video' - when /pdf/ then 'pdf' - else 'file' - end.inquiry - end + # Returns true if the content_type of this blob is in the image range, like image/png. + def image?() content_type =~ /^image/ end + + # Returns true if the content_type of this blob is in the audio range, like audio/mpeg. + def audio?() content_type =~ /^audio/ end + + # Returns true if the content_type of this blob is in the video range, like video/mp4. + def video?() content_type =~ /^video/ end + + # Returns true if the content_type of this blob is in the text range, like text/plain. + def text?() content_type =~ /^text/ end # Returns a `ActiveStorage::Variant` instance with the set of `transformations` passed in. This is only relevant # for image files, and it allows any image to be transformed for size, colors, and the like. Example: diff --git a/test/models/blob_test.rb b/test/models/blob_test.rb index b51be7a93b..8b14bcec87 100644 --- a/test/models/blob_test.rb +++ b/test/models/blob_test.rb @@ -11,10 +11,10 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase assert_equal Digest::MD5.base64digest(data), blob.checksum end - test "inquery type" do + test "text?" do blob = create_blob data: "Hello world!" - assert blob.type.text? - assert_not blob.type.audio? + assert blob.text? + assert_not blob.audio? end test "download yields chunks" do From 39bfc836b8eb7482acf2784423414895b6d876c6 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Sun, 30 Jul 2017 11:00:55 -0400 Subject: [PATCH 286/289] Configure per-service request headers for direct uploads (#83) * Configure per-service request headers for direct uploads * Fix header hashes --- app/assets/javascripts/activestorage.js | 2 +- .../direct_uploads_controller.rb | 9 ++++- app/javascript/activestorage/blob_record.js | 6 ++- app/javascript/activestorage/blob_upload.js | 9 +++-- app/models/active_storage/blob.rb | 9 +++-- app/models/active_storage/filename.rb | 8 ++++ lib/active_storage/service.rb | 7 +++- lib/active_storage/service/disk_service.rb | 4 ++ lib/active_storage/service/gcs_service.rb | 4 ++ lib/active_storage/service/s3_service.rb | 4 ++ .../direct_uploads_controller_test.rb | 39 ++++++++++++++----- 11 files changed, 81 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/activestorage.js b/app/assets/javascripts/activestorage.js index 8efea079e9..33dc5cdc58 100644 --- a/app/assets/javascripts/activestorage.js +++ b/app/assets/javascripts/activestorage.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.bubbles,i=r.cancelable,a=r.detail,u=document.createEvent("Event");return u.initEvent(e,n||!0,i||!0),u.detail=a||{},t.dispatchEvent(u),u}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r=200&&r<300?(this.attributes.signed_id=n.signed_blob_id,this.uploadURL=n.upload_to_url,this.callback(null,this.toJSON())):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.xhr.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])}); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.bubbles,i=r.cancelable,a=r.detail,u=document.createEvent("Event");return u.initEvent(e,n||!0,i||!0),u.detail=a||{},t.dispatchEvent(u),u}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r=200&&r<300){var i=n.direct_upload;delete n.direct_upload,this.attributes=n,this.directUploadData=i,this.callback(null,this.toJSON())}else this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.xhr.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])}); \ No newline at end of file diff --git a/app/controllers/active_storage/direct_uploads_controller.rb b/app/controllers/active_storage/direct_uploads_controller.rb index d42c52913a..0d93985897 100644 --- a/app/controllers/active_storage/direct_uploads_controller.rb +++ b/app/controllers/active_storage/direct_uploads_controller.rb @@ -4,11 +4,18 @@ class ActiveStorage::DirectUploadsController < ActionController::Base def create blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args) - render json: { upload_to_url: blob.service_url_for_direct_upload, signed_blob_id: blob.signed_id } + render json: direct_upload_json(blob) end private def blob_args params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys end + + def direct_upload_json(blob) + blob.as_json(methods: :signed_id).merge(direct_upload: { + url: blob.service_url_for_direct_upload, + headers: blob.service_headers_for_direct_upload + }) + end end diff --git a/app/javascript/activestorage/blob_record.js b/app/javascript/activestorage/blob_record.js index 9b7801afd5..3c6e6b6ba1 100644 --- a/app/javascript/activestorage/blob_record.js +++ b/app/javascript/activestorage/blob_record.js @@ -30,8 +30,10 @@ export class BlobRecord { requestDidLoad(event) { const { status, response } = this.xhr if (status >= 200 && status < 300) { - this.attributes.signed_id = response.signed_blob_id - this.uploadURL = response.upload_to_url + const { direct_upload } = response + delete response.direct_upload + this.attributes = response + this.directUploadData = direct_upload this.callback(null, this.toJSON()) } else { this.requestDidError(event) diff --git a/app/javascript/activestorage/blob_upload.js b/app/javascript/activestorage/blob_upload.js index c72820b433..99bf0c9e30 100644 --- a/app/javascript/activestorage/blob_upload.js +++ b/app/javascript/activestorage/blob_upload.js @@ -3,10 +3,13 @@ export class BlobUpload { this.blob = blob this.file = blob.file + const { url, headers } = blob.directUploadData + this.xhr = new XMLHttpRequest - this.xhr.open("PUT", blob.uploadURL, true) - this.xhr.setRequestHeader("Content-Type", blob.attributes.content_type) - this.xhr.setRequestHeader("Content-MD5", blob.attributes.checksum) + this.xhr.open("PUT", url, true) + for (const key in headers) { + this.xhr.setRequestHeader(key, headers[key]) + } this.xhr.addEventListener("load", event => this.requestDidLoad(event)) this.xhr.addEventListener("error", event => this.requestDidError(event)) } diff --git a/app/models/active_storage/blob.rb b/app/models/active_storage/blob.rb index 0f9562e23b..9208d36ee3 100644 --- a/app/models/active_storage/blob.rb +++ b/app/models/active_storage/blob.rb @@ -63,14 +63,14 @@ def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: end end - + # Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering. # It uses the framework-wide verifier on `ActiveStorage.verifier`, but with a dedicated purpose. def signed_id ActiveStorage.verifier.generate(id, purpose: :blob_id) end - # Returns the key pointing to the file on the service that's associated with this blob. The key is in the + # Returns the key pointing to the file on the service that's associated with this blob. The key is in the # standard secure-token format from Rails. So it'll look like: XTAPjJCJiuDrLk3TmwyJGpUo. This key is not intended # to be revealed directly to the user. Always refer to blobs using the signed_id or a verified form of the key. def key @@ -130,6 +130,10 @@ def service_url_for_direct_upload(expires_in: 5.minutes) service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size, checksum: checksum end + # Returns a Hash of headers for `service_url_for_direct_upload` requests. + def service_headers_for_direct_upload + service.headers_for_direct_upload key, filename: filename, content_type: content_type, content_length: byte_size, checksum: checksum + end # Uploads the `io` to the service on the `key` for this blob. Blobs are intended to be immutable, so you shouldn't be # using this method after a file has already been uploaded to fit with a blob. If you want to create a derivative blob, @@ -176,7 +180,6 @@ def purge_later ActiveStorage::PurgeJob.perform_later(self) end - private def compute_checksum_in_chunks(io) Digest::MD5.new.tap do |checksum| diff --git a/app/models/active_storage/filename.rb b/app/models/active_storage/filename.rb index 8605e4960c..35f4a8ac59 100644 --- a/app/models/active_storage/filename.rb +++ b/app/models/active_storage/filename.rb @@ -35,6 +35,14 @@ def to_s sanitized.to_s end + def as_json(*) + to_s + end + + def to_json + to_s + end + def <=>(other) to_s.downcase <=> other.to_s.downcase end diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index e6361318a8..b648c51823 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -87,13 +87,18 @@ def url(key, expires_in:, disposition:, filename:, content_type:) end # Returns a signed, temporary URL that a direct upload file can be PUT to on the `key`. - # The URL will be valid for the amount of seconds specified in `expires_in`. + # The URL will be valid for the amount of seconds specified in `expires_in`. # You most also provide the `content_type`, `content_length`, and `checksum` of the file # that will be uploaded. All these attributes will be validated by the service upon upload. def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) raise NotImplementedError end + # Returns a Hash of headers for `url_for_direct_upload` requests. + def headers_for_direct_upload(key, filename:, content_type:, content_length:, checksum:) + {} + end + private def instrument(operation, key, payload = {}, &block) ActiveSupport::Notifications.instrument( diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 7bc8d311da..35b0909297 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -98,6 +98,10 @@ def url_for_direct_upload(key, expires_in:, content_type:, content_length:, chec end end + def headers_for_direct_upload(key, content_type:, **) + { "Content-Type" => content_type } + end + private def path_for(key) File.join root, folder_for(key), key diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index d681a3dc45..bff2f9366e 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -68,6 +68,10 @@ def url_for_direct_upload(key, expires_in:, content_type:, content_length:, chec end end + def headers_for_direct_upload(key, content_type:, checksum:, **) + { "Content-Type" => content_type, "Content-MD5" => checksum } + end + private def file_for(key) bucket.file(key) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index c21977044d..ca461c2994 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -72,6 +72,10 @@ def url_for_direct_upload(key, expires_in:, content_type:, content_length:, chec end end + def headers_for_direct_upload(key, content_type:, checksum:, **) + { "Content-Type" => content_type, "Content-MD5" => checksum } + end + private def object_for(key) bucket.object(key) diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index f15fcff314..7ffa77ea73 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -13,12 +13,19 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionDispatch::Integration end test "creating new direct upload" do + checksum = Digest::MD5.base64digest("Hello") + post rails_direct_uploads_url, params: { blob: { - filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } + filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain" } } response.parsed_body.tap do |details| - assert_match(/#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["upload_to_url"]) - assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s + assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed(details["signed_id"]) + assert_equal "hello.txt", details["filename"] + assert_equal 6, details["byte_size"] + assert_equal checksum, details["checksum"] + assert_equal "text/plain", details["content_type"] + assert_match /#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["direct_upload"]["url"] + assert_equal({ "Content-Type" => "text/plain", "Content-MD5" => checksum }, details["direct_upload"]["headers"]) end end end @@ -40,12 +47,19 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionDispatch::Integratio end test "creating new direct upload" do + checksum = Digest::MD5.base64digest("Hello") + post rails_direct_uploads_url, params: { blob: { - filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } + filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain" } } @response.parsed_body.tap do |details| - assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["upload_to_url"] - assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s + assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed(details["signed_id"]) + assert_equal "hello.txt", details["filename"] + assert_equal 6, details["byte_size"] + assert_equal checksum, details["checksum"] + assert_equal "text/plain", details["content_type"] + assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["direct_upload"]["url"] + assert_equal({ "Content-Type" => "text/plain", "Content-MD5" => checksum }, details["direct_upload"]["headers"]) end end end @@ -55,12 +69,19 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionDispatch::Integratio class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::IntegrationTest test "creating new direct upload" do + checksum = Digest::MD5.base64digest("Hello") + post rails_direct_uploads_url, params: { blob: { - filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } + filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain" } } @response.parsed_body.tap do |details| - assert_match /rails\/active_storage\/disk/, details["upload_to_url"] - assert_equal "hello.txt", ActiveStorage::Blob.find_signed(details["signed_blob_id"]).filename.to_s + assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed(details["signed_id"]) + assert_equal "hello.txt", details["filename"] + assert_equal 6, details["byte_size"] + assert_equal checksum, details["checksum"] + assert_equal "text/plain", details["content_type"] + assert_match /rails\/active_storage\/disk/, details["direct_upload"]["url"] + assert_equal({ "Content-Type" => "text/plain" }, details["direct_upload"]["headers"]) end end end From 9da2c41b2fbdd7cc7e31473376e339e566900ade Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Sun, 30 Jul 2017 11:34:39 -0400 Subject: [PATCH 287/289] activestorage.js 0.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57d62ecc5c..299898017c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "activestorage", - "version": "0.1.0", + "version": "0.1.1", "description": "Attach cloud and local files in Rails applications", "main": "app/assets/javascripts/activestorage.js", "files": [ From 6c68524b69e1bfb28f0d1aa5967fd602ed1b7ed7 Mon Sep 17 00:00:00 2001 From: Dino Maric Date: Mon, 31 Jul 2017 16:41:00 +0200 Subject: [PATCH 288/289] Depend on Ruby >=2.2.2 (#85) --- activestorage.gemspec | 2 +- lib/active_storage/service/gcs_service.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/activestorage.gemspec b/activestorage.gemspec index 9546b60783..eeefa03903 100644 --- a/activestorage.gemspec +++ b/activestorage.gemspec @@ -7,7 +7,7 @@ s.homepage = "https://github.com/rails/activestorage" s.license = "MIT" - s.required_ruby_version = ">= 2.3.0" + s.required_ruby_version = ">= 2.2.2" s.add_dependency "rails", ">= 5.2.0.alpha" diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index bff2f9366e..73629f7486 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -32,7 +32,7 @@ def download(key) def delete(key) instrument :delete, key do - file_for(key)&.delete + file_for(key).try(:delete) end end From 3f4a7218a4a4923a0e7ce1b2eb0d2888ce30da58 Mon Sep 17 00:00:00 2001 From: Dino Maric Date: Mon, 31 Jul 2017 17:09:12 +0200 Subject: [PATCH 289/289] Azure Storage support (#36) * Microsoft Azure storage support * Add support for Microsoft Azure Storage * Comply with the new headers implementation --- Gemfile | 3 + Gemfile.lock | 18 +++ config/storage_services.yml | 7 ++ lib/active_storage/service/azure_service.rb | 115 ++++++++++++++++++ .../direct_uploads_controller_test.rb | 34 ++++++ test/service/azure_service_test.rb | 14 +++ test/service/configurations-example.yml | 7 ++ 7 files changed, 198 insertions(+) create mode 100644 lib/active_storage/service/azure_service.rb create mode 100644 test/service/azure_service_test.rb diff --git a/Gemfile b/Gemfile index 55b0deec27..b8b6e80176 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,9 @@ gem "httparty" gem "aws-sdk", "~> 2", require: false gem "google-cloud-storage", "~> 1.3", require: false +# Contains fix to be able to test using StringIO +gem 'azure-core', git: "https://github.com/dixpac/azure-ruby-asm-core.git" +gem 'azure-storage', require: false gem "mini_magick" diff --git a/Gemfile.lock b/Gemfile.lock index 4319b1e22d..7388096778 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,12 @@ +GIT + remote: https://github.com/dixpac/azure-ruby-asm-core.git + revision: 4403389747f44a94b73e7a7522d1ea11f8b1a266 + specs: + azure-core (0.1.8) + faraday (~> 0.9) + faraday_middleware (~> 0.10) + nokogiri (~> 1.7) + GIT remote: https://github.com/rails/rails.git revision: 127b475dc251a06942fe0cd2de2e0545cf5ed69f @@ -79,6 +88,11 @@ GEM aws-sdk-resources (2.10.7) aws-sdk-core (= 2.10.7) aws-sigv4 (1.0.0) + azure-storage (0.11.4.preview) + azure-core (~> 0.1) + faraday (~> 0.9) + faraday_middleware (~> 0.10) + nokogiri (~> 1.6) builder (3.2.3) byebug (9.0.6) concurrent-ruby (1.0.5) @@ -88,6 +102,8 @@ GEM erubi (1.6.1) faraday (0.12.1) multipart-post (>= 1.2, < 3) + faraday_middleware (0.12.0) + faraday (>= 0.7.4, < 1.0) globalid (0.4.0) activesupport (>= 4.2.0) google-api-client (0.13.0) @@ -201,6 +217,8 @@ PLATFORMS DEPENDENCIES activestorage! aws-sdk (~> 2) + azure-core! + azure-storage bundler (~> 1.15) byebug google-cloud-storage (~> 1.3) diff --git a/config/storage_services.yml b/config/storage_services.yml index c80a3e8453..057e15e74d 100644 --- a/config/storage_services.yml +++ b/config/storage_services.yml @@ -21,6 +21,13 @@ google: keyfile: <%= Rails.root.join("path/to/gcs.keyfile") %> bucket: your_own_bucket +microsoft: + service: Azure + path: your_azure_storage_path + storage_account_name: your_account_name + storage_access_key: <%= Rails.application.secrets.azure[:secret_access_key] %> + container: your_container_name + mirror: service: Mirror primary: local diff --git a/lib/active_storage/service/azure_service.rb b/lib/active_storage/service/azure_service.rb new file mode 100644 index 0000000000..a505b9a0ee --- /dev/null +++ b/lib/active_storage/service/azure_service.rb @@ -0,0 +1,115 @@ +require "active_support/core_ext/numeric/bytes" +require "azure/storage" +require "azure/storage/core/auth/shared_access_signature" + +# Wraps the Microsoft Azure Storage Blob Service as a Active Storage service. +# See `ActiveStorage::Service` for the generic API documentation that applies to all services. +class ActiveStorage::Service::AzureService < ActiveStorage::Service + attr_reader :client, :path, :blobs, :container, :signer + + def initialize(path:, storage_account_name:, storage_access_key:, container:) + @client = Azure::Storage::Client.create(storage_account_name: storage_account_name, storage_access_key: storage_access_key) + @signer = Azure::Storage::Core::Auth::SharedAccessSignature.new(storage_account_name, storage_access_key) + @blobs = client.blob_client + @container = container + @path = path + end + + def upload(key, io, checksum: nil) + instrument :upload, key, checksum: checksum do + begin + blobs.create_block_blob(container, key, io, content_md5: checksum) + rescue Azure::Core::Http::HTTPError => e + raise ActiveStorage::IntegrityError + end + end + end + + def download(key) + if block_given? + instrument :streaming_download, key do + stream(key, &block) + end + else + instrument :download, key do + _, io = blobs.get_blob(container, key) + io.force_encoding(Encoding::BINARY) + end + end + end + + def delete(key) + instrument :delete, key do + begin + blobs.delete_blob(container, key) + rescue Azure::Core::Http::HTTPError + false + end + end + end + + def exist?(key) + instrument :exist, key do |payload| + answer = blob_for(key).present? + payload[:exist] = answer + answer + end + end + + def url(key, expires_in:, disposition:, filename:) + instrument :url, key do |payload| + base_url = url_for(key) + generated_url = signer.signed_uri(URI(base_url), false, permissions: "r", + expiry: format_expiry(expires_in), content_disposition: "#{disposition}; filename=\"#{filename}\"").to_s + + payload[:url] = generated_url + + generated_url + end + end + + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) + instrument :url, key do |payload| + base_url = url_for(key) + generated_url = signer.signed_uri(URI(base_url), false, permissions: "rw", + expiry: format_expiry(expires_in)).to_s + + payload[:url] = generated_url + + generated_url + end + end + + def headers_for_direct_upload(key, content_type:, checksum:, **) + { "Content-Type" => content_type, "Content-MD5" => checksum, "x-ms-blob-type" => "BlockBlob" } + end + + private + def url_for(key) + "#{path}/#{container}/#{key}" + end + + def blob_for(key) + blobs.get_blob_properties(container, key) + rescue Azure::Core::Http::HTTPError + false + end + + def format_expiry(expires_in) + expires_in ? Time.now.utc.advance(seconds: expires_in).iso8601 : nil + end + + # Reads the object for the given key in chunks, yielding each to the block. + def stream(key, options = {}, &block) + blob = blob_for(key) + + chunk_size = 5.megabytes + offset = 0 + + while offset < blob.properties[:content_length] + _, io = blobs.get_blob(container, key, start_range: offset, end_range: offset + chunk_size - 1) + yield io + offset += chunk_size + end + end +end diff --git a/test/controllers/direct_uploads_controller_test.rb b/test/controllers/direct_uploads_controller_test.rb index 7ffa77ea73..7185bf2737 100644 --- a/test/controllers/direct_uploads_controller_test.rb +++ b/test/controllers/direct_uploads_controller_test.rb @@ -67,6 +67,40 @@ class ActiveStorage::GCSDirectUploadsControllerTest < ActionDispatch::Integratio puts "Skipping GCS Direct Upload tests because no GCS configuration was supplied" end +if SERVICE_CONFIGURATIONS[:azure] + class ActiveStorage::AzureDirectUploadsControllerTest < ActionDispatch::IntegrationTest + setup do + @config = SERVICE_CONFIGURATIONS[:azure] + + @old_service = ActiveStorage::Blob.service + ActiveStorage::Blob.service = ActiveStorage::Service.configure(:azure, SERVICE_CONFIGURATIONS) + end + + teardown do + ActiveStorage::Blob.service = @old_service + end + + test "creating new direct upload" do + checksum = Digest::MD5.base64digest("Hello") + + post rails_direct_uploads_url, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: checksum, content_type: "text/plain" } } + + @response.parsed_body.tap do |details| + assert_equal ActiveStorage::Blob.find(details["id"]), ActiveStorage::Blob.find_signed(details["signed_id"]) + assert_equal "hello.txt", details["filename"] + assert_equal 6, details["byte_size"] + assert_equal checksum, details["checksum"] + assert_equal "text/plain", details["content_type"] + assert_match %r{#{@config[:storage_account_name]}\.blob\.core\.windows\.net/#{@config[:container]}}, details["direct_upload"]["url"] + assert_equal({ "Content-Type" => "text/plain", "Content-MD5" => checksum, "x-ms-blob-type" => "BlockBlob" }, details["direct_upload"]["headers"]) + end + end + end +else + puts "Skipping Azure Direct Upload tests because no Azure configuration was supplied" +end + class ActiveStorage::DiskDirectUploadsControllerTest < ActionDispatch::IntegrationTest test "creating new direct upload" do checksum = Digest::MD5.base64digest("Hello") diff --git a/test/service/azure_service_test.rb b/test/service/azure_service_test.rb new file mode 100644 index 0000000000..0ddbac83e7 --- /dev/null +++ b/test/service/azure_service_test.rb @@ -0,0 +1,14 @@ +require "service/shared_service_tests" +require "httparty" +require "uri" + +if SERVICE_CONFIGURATIONS[:azure] + class ActiveStorage::Service::AzureServiceTest < ActiveSupport::TestCase + SERVICE = ActiveStorage::Service.configure(:azure, SERVICE_CONFIGURATIONS) + + include ActiveStorage::Service::SharedServiceTests + end + +else + puts "Skipping Azure Storage Service tests because no Azure configuration was supplied" +end diff --git a/test/service/configurations-example.yml b/test/service/configurations-example.yml index 8bcc57f05a..68f6ae4224 100644 --- a/test/service/configurations-example.yml +++ b/test/service/configurations-example.yml @@ -22,3 +22,10 @@ gcs: } project: bucket: + +azure: + service: Azure + path: "" + storage_account_name: "" + storage_access_key: "" + container: ""

W2f9R-$lpFBs-%RgTn7<-ZaN3^f*1A|faO}) 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 lvxvb;i6z131i~*n+Z=y6 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%0D5=Pwys$&#jGldT~Z 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 J$PU=KdA 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 literal 0 HcmV?d00001 diff --git a/test/test_helper.rb b/test/test_helper.rb index 03593b12c7..878ce8391c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -24,6 +24,9 @@ require "active_storage/verified_key_with_expiration" ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") +require "active_storage/variant" +ActiveStorage::Variant.verifier = ActiveSupport::MessageVerifier.new("Testing") + class ActiveSupport::TestCase private def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") diff --git a/test/variation_test.rb b/test/variation_test.rb new file mode 100644 index 0000000000..3b05095292 --- /dev/null +++ b/test/variation_test.rb @@ -0,0 +1,16 @@ +require "test_helper" +require "database/setup" +require "active_storage/variant" + +class ActiveStorage::VariationTest < ActiveSupport::TestCase + test "square variation" do + blob = ActiveStorage::Blob.create_after_upload! \ + io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)), filename: "racecar.jpg", content_type: "image/jpeg" + + variation_key = ActiveStorage::Variant.encode_key(resize: "500x500") + + variant = ActiveStorage::Variant.lookup(blob_key: blob.key, variation_key: variation_key) + + assert_match /racecar.jpg/, variant.url + end +end From 6d3962461fb8d35fc9538d685fee96267663acf2 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Tue, 11 Jul 2017 14:09:39 -0600 Subject: [PATCH 160/289] S3: slim down service implementation (#40) * Use simple core API for duck-type compat with other clients * initialize: accept an existing client * initialize: accept arbitrary client args instead of a fixed, required set * download: use native get_object streaming, no need to implement range requests * exists?: use head_object (which returns immediately) rather than waiting for existence --- lib/active_storage/service/s3_service.rb | 60 ++++++++++-------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index c3b6688bb9..ad55db0dc0 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -4,84 +4,72 @@ class ActiveStorage::Service::S3Service < ActiveStorage::Service attr_reader :client, :bucket - def initialize(access_key_id:, secret_access_key:, region:, bucket:) - @client = Aws::S3::Resource.new(access_key_id: access_key_id, secret_access_key: secret_access_key, region: region) - @bucket = @client.bucket(bucket) + def initialize(bucket:, client: nil, **client_options) + @bucket = bucket + @client = client || Aws::S3::Client.new(client_options) end def upload(key, io, checksum: nil) instrument :upload, key, checksum: checksum do begin - object_for(key).put(body: io, content_md5: checksum) + client.put_object bucket: bucket, key: key, body: io, content_md5: checksum rescue Aws::S3::Errors::BadDigest raise ActiveStorage::IntegrityError end end end - def download(key) + def download(key, &block) if block_given? instrument :streaming_download, key do - stream(key, &block) + client.get_object bucket: bucket, key: key, &block end else instrument :download, key do - object_for(key).get.body.read.force_encoding(Encoding::BINARY) + "".b.tap do |data| + client.get_object bucket: bucket, key: key, response_target: data + end end end end def delete(key) instrument :delete, key do - object_for(key).delete + client.delete_object bucket: bucket, key: key end end def exist?(key) instrument :exist, key do |payload| - answer = object_for(key).exists? - payload[:exist] = answer - answer + payload[:exist] = + begin + client.head_object bucket: bucket, key: key + rescue Aws::S3::Errors::NoSuckKey + false + else + true + end end end def url(key, expires_in:, disposition:, filename:) instrument :url, key do |payload| - generated_url = object_for(key).presigned_url :get, expires_in: expires_in, + payload[:url] = presigner.presigned_url :get_object, + bucket: bucket, key: key, expires_in: expires_in, response_content_disposition: "#{disposition}; filename=\"#{filename}\"" - - payload[:url] = generated_url - - generated_url end end def url_for_direct_upload(key, expires_in:, content_type:, content_length:) instrument :url, key do |payload| - generated_url = object_for(key).presigned_url :put, expires_in: expires_in, + payload[:url] = presigner.presigned_url :put_object, + bucket: bucket, key: key, expires_in: expires_in, content_type: content_type, content_length: content_length - - payload[:url] = generated_url - - generated_url end end private - def object_for(key) - bucket.object(key) - end - - # Reads the object for the given key in chunks, yielding each to the block. - def stream(key, options = {}, &block) - object = object_for(key) - - chunk_size = 5.megabytes - offset = 0 - - while offset < object.content_length - yield object.read(options.merge(range: "bytes=#{offset}-#{offset + chunk_size - 1}")) - offset += chunk_size - end + def presigner + @presigner ||= Aws::S3::Presigner.new client: client end end From 17906fd22f5c6bbb56f10ee3221a62569fb0d5c6 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 12 Jul 2017 08:44:08 +0200 Subject: [PATCH 161/289] Revert "S3: slim down service implementation (#40)" (#41) This reverts commit 6d3962461fb8d35fc9538d685fee96267663acf2. --- lib/active_storage/service/s3_service.rb | 60 ++++++++++++++---------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index ad55db0dc0..c3b6688bb9 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -4,72 +4,84 @@ class ActiveStorage::Service::S3Service < ActiveStorage::Service attr_reader :client, :bucket - def initialize(bucket:, client: nil, **client_options) - @bucket = bucket - @client = client || Aws::S3::Client.new(client_options) + def initialize(access_key_id:, secret_access_key:, region:, bucket:) + @client = Aws::S3::Resource.new(access_key_id: access_key_id, secret_access_key: secret_access_key, region: region) + @bucket = @client.bucket(bucket) end def upload(key, io, checksum: nil) instrument :upload, key, checksum: checksum do begin - client.put_object bucket: bucket, key: key, body: io, content_md5: checksum + object_for(key).put(body: io, content_md5: checksum) rescue Aws::S3::Errors::BadDigest raise ActiveStorage::IntegrityError end end end - def download(key, &block) + def download(key) if block_given? instrument :streaming_download, key do - client.get_object bucket: bucket, key: key, &block + stream(key, &block) end else instrument :download, key do - "".b.tap do |data| - client.get_object bucket: bucket, key: key, response_target: data - end + object_for(key).get.body.read.force_encoding(Encoding::BINARY) end end end def delete(key) instrument :delete, key do - client.delete_object bucket: bucket, key: key + object_for(key).delete end end def exist?(key) instrument :exist, key do |payload| - payload[:exist] = - begin - client.head_object bucket: bucket, key: key - rescue Aws::S3::Errors::NoSuckKey - false - else - true - end + answer = object_for(key).exists? + payload[:exist] = answer + answer end end def url(key, expires_in:, disposition:, filename:) instrument :url, key do |payload| - payload[:url] = presigner.presigned_url :get_object, - bucket: bucket, key: key, expires_in: expires_in, + generated_url = object_for(key).presigned_url :get, expires_in: expires_in, response_content_disposition: "#{disposition}; filename=\"#{filename}\"" + + payload[:url] = generated_url + + generated_url end end def url_for_direct_upload(key, expires_in:, content_type:, content_length:) instrument :url, key do |payload| - payload[:url] = presigner.presigned_url :put_object, - bucket: bucket, key: key, expires_in: expires_in, + generated_url = object_for(key).presigned_url :put, expires_in: expires_in, content_type: content_type, content_length: content_length + + payload[:url] = generated_url + + generated_url end end private - def presigner - @presigner ||= Aws::S3::Presigner.new client: client + def object_for(key) + bucket.object(key) + end + + # Reads the object for the given key in chunks, yielding each to the block. + def stream(key, options = {}, &block) + object = object_for(key) + + chunk_size = 5.megabytes + offset = 0 + + while offset < object.content_length + yield object.read(options.merge(range: "bytes=#{offset}-#{offset + chunk_size - 1}")) + offset += chunk_size + end end end From afb7047e52dd6dae160b60b74a0c7efaf536933c Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 12 Jul 2017 10:01:12 -0600 Subject: [PATCH 162/289] Update GCSService#url Update google-cloud-storage dependency to 1.3 Refactor arguments to Google::Cloud::Storage::File#signed_url --- Gemfile | 2 +- Gemfile.lock | 6 +++--- lib/active_storage/service/gcs_service.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index a757a5c793..75e07016da 100644 --- a/Gemfile +++ b/Gemfile @@ -9,4 +9,4 @@ gem 'sqlite3' gem 'httparty' gem 'aws-sdk', '~> 2', require: false -gem 'google-cloud-storage', require: false +gem 'google-cloud-storage', '~> 1.3', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 7e4c6f78f2..56290db48d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,9 +69,9 @@ GEM google-cloud-core (1.0.0) google-cloud-env (~> 1.0) googleauth (~> 0.5.1) - google-cloud-env (1.0.0) + google-cloud-env (1.0.1) faraday (~> 0.11) - google-cloud-storage (1.2.0) + google-cloud-storage (1.3.0) digest-crc (~> 0.4) google-api-client (~> 0.13.0) google-cloud-core (~> 1.0) @@ -141,7 +141,7 @@ DEPENDENCIES aws-sdk (~> 2) bundler (~> 1.15) byebug - google-cloud-storage + google-cloud-storage (~> 1.3) httparty rake sqlite3 diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index bca4ab5331..0bcd29cab8 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -44,8 +44,8 @@ def exist?(key) def url(key, expires_in:, disposition:, filename:) instrument :url, key do |payload| - generated_url = file_for(key).signed_url(expires: expires_in) + "&" + - { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"" }.to_query + query = { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"" } + generated_url = file_for(key).signed_url(expires: expires_in, query: query) payload[:url] = generated_url From 2a738b31c538c0627a46504733fca3c914e1724e Mon Sep 17 00:00:00 2001 From: dixpac Date: Thu, 13 Jul 2017 20:39:02 +0200 Subject: [PATCH 163/289] Remove few ivars from gcs_service implementation --- lib/active_storage/service/gcs_service.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index 0bcd29cab8..1addda6733 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -36,20 +36,14 @@ def delete(key) def exist?(key) instrument :exist, key do |payload| - answer = file_for(key).present? - payload[:exist] = answer - answer + payload[:exist] = file_for(key).present? end end def url(key, expires_in:, disposition:, filename:) instrument :url, key do |payload| query = { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"" } - generated_url = file_for(key).signed_url(expires: expires_in, query: query) - - payload[:url] = generated_url - - generated_url + payload[:url] = file_for(key).signed_url(expires: expires_in, query: query) end end From 14e6386b3ceb0ab1d13ddd1353722d56785f9007 Mon Sep 17 00:00:00 2001 From: Dino Maric Date: Thu, 13 Jul 2017 21:54:06 +0200 Subject: [PATCH 164/289] Fix regular expression on s3 test URL generation test (#44) So tests are passing if the bucket name is rails-active storage. But developers specify their own s3 tests configuration (in my case was activestorage-test) then this regex fails. Also the first part is dynamic and based on bucket name and region --- test/service/s3_service_test.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index 167aa78a17..4875ac908b 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -16,7 +16,7 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase key = SecureRandom.base58(24) data = "Something else entirely!" direct_upload_url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size) - + url = URI.parse(direct_upload_url).to_s.split("?").first query = CGI::parse(URI.parse(direct_upload_url).query).collect { |(k, v)| [ k, v.first ] }.to_h @@ -30,16 +30,16 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase }, debug_output: STDOUT ) - + assert_equal data, @service.download(key) ensure @service.delete key end end - + test "signed URL generation" do - assert_match /rails-activestorage\.s3\.amazonaws\.com.*response-content-disposition=inline.*avatar\.png/, - @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") + assert_match /.+s3.+amazonaws.com.*response-content-disposition=inline.*avatar\.png/, + @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") end end else From 6dcdc5c9abb8e4e93a2c582b11ff4bb77d62ed3b Mon Sep 17 00:00:00 2001 From: Cristian Bica Date: Fri, 14 Jul 2017 01:09:56 +0300 Subject: [PATCH 165/289] Added rubocop / codeclimate config and fixed current offenses (#45) --- .codeclimate.yml | 7 + .rubocop.yml | 125 ++++++++++++++++++ Gemfile | 16 ++- Gemfile.lock | 17 +++ lib/active_storage/disk_controller.rb | 2 +- lib/active_storage/download.rb | 6 +- lib/active_storage/service.rb | 2 +- lib/active_storage/service/disk_service.rb | 2 +- lib/active_storage/service/s3_service.rb | 8 +- .../verified_key_with_expiration.rb | 2 +- lib/tasks/activestorage.rake | 2 +- test/attachments_test.rb | 4 +- test/blob_test.rb | 6 +- test/database/setup.rb | 2 +- test/filename_test.rb | 10 +- test/service/configurator_test.rb | 1 - test/service/disk_service_test.rb | 2 +- test/service/mirror_service_test.rb | 2 +- 18 files changed, 183 insertions(+), 33 deletions(-) create mode 100644 .codeclimate.yml create mode 100644 .rubocop.yml diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000000..18f3dbb7b5 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,7 @@ +engines: + rubocop: + enabled: true + +ratings: + paths: + - "**.rb" diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..7b4478d3bd --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,125 @@ +AllCops: + TargetRubyVersion: 2.3 + # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop + # to ignore them, so only the ones explicitly set in this file are enabled. + DisabledByDefault: true + Exclude: + - '**/templates/**/*' + - '**/vendor/**/*' + - 'actionpack/lib/action_dispatch/journey/parser.rb' + +# Prefer &&/|| over and/or. +Style/AndOr: + Enabled: true + +# Do not use braces for hash literals when they are the last argument of a +# method call. +Style/BracesAroundHashParameters: + Enabled: true + EnforcedStyle: context_dependent + +# Align `when` with `case`. +Style/CaseIndentation: + Enabled: true + +# Align comments with method definitions. +Style/CommentIndentation: + Enabled: true + +# No extra empty lines. +Style/EmptyLines: + Enabled: false + +# In a regular class definition, no empty lines around the body. +Style/EmptyLinesAroundClassBody: + Enabled: true + +# In a regular method definition, no empty lines around the body. +Style/EmptyLinesAroundMethodBody: + Enabled: true + +# In a regular module definition, no empty lines around the body. +Style/EmptyLinesAroundModuleBody: + Enabled: true + +# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. +Style/HashSyntax: + Enabled: true + +# Method definitions after `private` or `protected` isolated calls need one +# extra level of indentation. +Style/IndentationConsistency: + Enabled: true + EnforcedStyle: rails + +# Two spaces, no tabs (for indentation). +Style/IndentationWidth: + Enabled: true + +Style/SpaceAfterColon: + Enabled: true + +Style/SpaceAfterComma: + Enabled: true + +Style/SpaceAroundEqualsInParameterDefault: + Enabled: true + +Style/SpaceAroundKeyword: + Enabled: true + +Style/SpaceAroundOperators: + Enabled: true + +Style/SpaceBeforeFirstArg: + Enabled: true + +# Defining a method with parameters needs parentheses. +Style/MethodDefParentheses: + Enabled: true + +# Use `foo {}` not `foo{}`. +Style/SpaceBeforeBlockBraces: + Enabled: true + +# Use `foo { bar }` not `foo {bar}`. +Style/SpaceInsideBlockBraces: + Enabled: true + +# Use `{ a: 1 }` not `{a:1}`. +Style/SpaceInsideHashLiteralBraces: + Enabled: true + +Style/SpaceInsideParens: + Enabled: true + +# Check quotes usage according to lint rule below. +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +# Detect hard tabs, no hard tabs. +Style/Tab: + Enabled: true + +# Blank lines should not have any spaces. +Style/TrailingBlankLines: + Enabled: true + +# No trailing whitespace. +Style/TrailingWhitespace: + Enabled: true + +# Use quotes for string literals when they are enough. +Style/UnneededPercentQ: + Enabled: true + +# Align `end` with the matching keyword or starting expression except for +# assignments, where it should be aligned with the LHS. +Lint/EndAlignment: + Enabled: true + EnforcedStyleAlignWith: variable + +# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. +Lint/RequireParentheses: + Enabled: true diff --git a/Gemfile b/Gemfile index 75e07016da..d2d6db9065 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,14 @@ -source 'https://rubygems.org' +source "https://rubygems.org" gemspec -gem 'rake' -gem 'byebug' +gem "rake" +gem "byebug" -gem 'sqlite3' -gem 'httparty' +gem "sqlite3" +gem "httparty" -gem 'aws-sdk', '~> 2', require: false -gem 'google-cloud-storage', '~> 1.3', require: false +gem "aws-sdk", "~> 2", require: false +gem "google-cloud-storage", "~> 1.3", require: false + +gem "rubocop", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 56290db48d..cce1d346da 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -40,6 +40,7 @@ GEM addressable (2.5.1) public_suffix (~> 2.0, >= 2.0.2) arel (8.0.0) + ast (2.3.0) aws-sdk (2.10.7) aws-sdk-resources (= 2.10.7) aws-sdk-core (2.10.7) @@ -107,6 +108,10 @@ GEM nokogiri (1.7.2) mini_portile2 (~> 2.1.0) os (0.9.6) + parallel (1.11.2) + parser (2.4.0.0) + ast (~> 2.2) + powerpack (0.1.1) public_suffix (2.0.5) rack (2.0.3) rack-test (0.6.3) @@ -116,12 +121,22 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) + rainbow (2.2.2) + rake rake (12.0.0) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) uber (< 0.2.0) retriable (3.0.2) + rubocop (0.49.1) + parallel (~> 1.10) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.8.1) signet (0.7.3) addressable (~> 2.3) faraday (~> 0.9) @@ -132,6 +147,7 @@ GEM tzinfo (1.2.3) thread_safe (~> 0.1) uber (0.1.0) + unicode-display_width (1.3.0) PLATFORMS ruby @@ -144,6 +160,7 @@ DEPENDENCIES google-cloud-storage (~> 1.3) httparty rake + rubocop sqlite3 BUNDLED WITH diff --git a/lib/active_storage/disk_controller.rb b/lib/active_storage/disk_controller.rb index 7149cc17a6..16a295d00d 100644 --- a/lib/active_storage/disk_controller.rb +++ b/lib/active_storage/disk_controller.rb @@ -33,6 +33,6 @@ def decode_verified_key end def disposition_param - params[:disposition].presence_in(%w( inline attachment )) || 'inline' + params[:disposition].presence_in(%w( inline attachment )) || "inline" end end diff --git a/lib/active_storage/download.rb b/lib/active_storage/download.rb index 4d656942d8..6040a32de9 100644 --- a/lib/active_storage/download.rb +++ b/lib/active_storage/download.rb @@ -14,7 +14,7 @@ class ActiveStorage::Download application/xhtml+xml ) - BINARY_CONTENT_TYPE = 'application/octet-stream' + BINARY_CONTENT_TYPE = "application/octet-stream" def initialize(stored_file) @stored_file = stored_file @@ -22,11 +22,11 @@ def initialize(stored_file) def headers(force_attachment: false) { - x_accel_redirect: '/reproxy', + x_accel_redirect: "/reproxy", x_reproxy_url: reproxy_url, content_type: content_type, content_disposition: content_disposition(force_attachment), - x_frame_options: 'SAMEORIGIN' + x_frame_options: "SAMEORIGIN" } end diff --git a/lib/active_storage/service.rb b/lib/active_storage/service.rb index d0d4362006..cba9cd9c83 100644 --- a/lib/active_storage/service.rb +++ b/lib/active_storage/service.rb @@ -85,7 +85,7 @@ def url_for_direct_upload(key, expires_in:, content_type:, content_length:) private def instrument(operation, key, payload = {}, &block) ActiveSupport::Notifications.instrument( - "service_#{operation}.active_storage", + "service_#{operation}.active_storage", payload.merge(key: key, service: service_name), &block) end diff --git a/lib/active_storage/service/disk_service.rb b/lib/active_storage/service/disk_service.rb index 7e64e1e909..a2a27528c1 100644 --- a/lib/active_storage/service/disk_service.rb +++ b/lib/active_storage/service/disk_service.rb @@ -20,7 +20,7 @@ def upload(key, io, checksum: nil) def download(key) if block_given? instrument :streaming_download, key do - File.open(path_for(key), 'rb') do |file| + File.open(path_for(key), "rb") do |file| while data = file.read(64.kilobytes) yield data end diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index c3b6688bb9..e75ac36c7d 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -49,9 +49,9 @@ def url(key, expires_in:, disposition:, filename:) instrument :url, key do |payload| generated_url = object_for(key).presigned_url :get, expires_in: expires_in, response_content_disposition: "#{disposition}; filename=\"#{filename}\"" - + payload[:url] = generated_url - + generated_url end end @@ -60,9 +60,9 @@ def url_for_direct_upload(key, expires_in:, content_type:, content_length:) instrument :url, key do |payload| generated_url = object_for(key).presigned_url :put, expires_in: expires_in, content_type: content_type, content_length: content_length - + payload[:url] = generated_url - + generated_url end end diff --git a/lib/active_storage/verified_key_with_expiration.rb b/lib/active_storage/verified_key_with_expiration.rb index 8708106735..e429ee21ce 100644 --- a/lib/active_storage/verified_key_with_expiration.rb +++ b/lib/active_storage/verified_key_with_expiration.rb @@ -1,5 +1,5 @@ class ActiveStorage::VerifiedKeyWithExpiration - class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveStorage') : nil + class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier("ActiveStorage") : nil class << self def encode(key, expires_in: nil) diff --git a/lib/tasks/activestorage.rake b/lib/tasks/activestorage.rake index 17dab0854a..ea83707224 100644 --- a/lib/tasks/activestorage.rake +++ b/lib/tasks/activestorage.rake @@ -7,7 +7,7 @@ namespace :activestorage do FileUtils.mkdir_p Rails.root.join("tmp/storage") puts "Made storage and tmp/storage directories for development and testing" - FileUtils.cp File.expand_path("../../active_storage/storage_services.yml", __FILE__), Rails.root.join("config") + FileUtils.cp File.expand_path("../../active_storage/storage_services.yml", __FILE__), Rails.root.join("config") puts "Copied default configuration to config/storage_services.yml" migration_file_path = "db/migrate/#{Time.now.utc.strftime("%Y%m%d%H%M%S")}_active_storage_create_tables.rb" diff --git a/test/attachments_test.rb b/test/attachments_test.rb index ec7e9fcd5b..9b88b18247 100644 --- a/test/attachments_test.rb +++ b/test/attachments_test.rb @@ -66,7 +66,7 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase test "attach new blobs" do @user.highlights.attach( - { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, + { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" }, { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }) assert_equal "town.jpg", @user.highlights.first.filename.to_s @@ -76,7 +76,7 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase test "purge attached blobs" do @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg") highlight_keys = @user.highlights.collect(&:key) - + @user.highlights.purge assert_not @user.highlights.attached? assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first) diff --git a/test/blob_test.rb b/test/blob_test.rb index cf27d59348..6a9765a859 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -13,7 +13,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase end test "download yields chunks" do - blob = create_blob data: 'a' * 75.kilobytes + blob = create_blob data: "a" * 75.kilobytes chunks = [] blob.download do |chunk| @@ -21,8 +21,8 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase end assert_equal 2, chunks.size - assert_equal 'a' * 64.kilobytes, chunks.first - assert_equal 'a' * 11.kilobytes, chunks.second + assert_equal "a" * 64.kilobytes, chunks.first + assert_equal "a" * 11.kilobytes, chunks.second end test "urls expiring in 5 minutes" do diff --git a/test/database/setup.rb b/test/database/setup.rb index 5921412b0c..b12038ee68 100644 --- a/test/database/setup.rb +++ b/test/database/setup.rb @@ -1,6 +1,6 @@ require "active_storage/migration" require_relative "create_users_migration" -ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") ActiveStorageCreateTables.migrate(:up) ActiveStorageCreateUsers.migrate(:up) diff --git a/test/filename_test.rb b/test/filename_test.rb index 448ba7f766..e448238675 100644 --- a/test/filename_test.rb +++ b/test/filename_test.rb @@ -4,8 +4,8 @@ class ActiveStorage::FilenameTest < ActiveSupport::TestCase test "sanitize" do "%$|:;/\t\r\n\\".each_char do |character| filename = ActiveStorage::Filename.new("foo#{character}bar.pdf") - assert_equal 'foo-bar.pdf', filename.sanitized - assert_equal 'foo-bar.pdf', filename.to_s + assert_equal "foo-bar.pdf", filename.sanitized + assert_equal "foo-bar.pdf", filename.to_s end end @@ -23,14 +23,14 @@ class ActiveStorage::FilenameTest < ActiveSupport::TestCase test "strips RTL override chars used to spoof unsafe executables as docs" do # Would be displayed in Windows as "evilexe.pdf" due to the right-to-left # (RTL) override char! - assert_equal 'evil-fdp.exe', ActiveStorage::Filename.new("evil\u{202E}fdp.exe").sanitized + assert_equal "evil-fdp.exe", ActiveStorage::Filename.new("evil\u{202E}fdp.exe").sanitized end test "compare case-insensitively" do - assert_operator ActiveStorage::Filename.new('foobar.pdf'), :==, ActiveStorage::Filename.new('FooBar.PDF') + assert_operator ActiveStorage::Filename.new("foobar.pdf"), :==, ActiveStorage::Filename.new("FooBar.PDF") end test "compare sanitized" do - assert_operator ActiveStorage::Filename.new('foo-bar.pdf'), :==, ActiveStorage::Filename.new("foo\tbar.pdf") + assert_operator ActiveStorage::Filename.new("foo-bar.pdf"), :==, ActiveStorage::Filename.new("foo\tbar.pdf") end end diff --git a/test/service/configurator_test.rb b/test/service/configurator_test.rb index f8e4dccc9c..c69b8d5087 100644 --- a/test/service/configurator_test.rb +++ b/test/service/configurator_test.rb @@ -12,4 +12,3 @@ class ActiveStorage::Service::ConfiguratorTest < ActiveSupport::TestCase end end end - diff --git a/test/service/disk_service_test.rb b/test/service/disk_service_test.rb index f7752b25ef..e9a96003f1 100644 --- a/test/service/disk_service_test.rb +++ b/test/service/disk_service_test.rb @@ -7,6 +7,6 @@ class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase test "url generation" do assert_match /rails\/active_storage\/disk\/.*\/avatar\.png\?disposition=inline/, - @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") + @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") end end diff --git a/test/service/mirror_service_test.rb b/test/service/mirror_service_test.rb index 8bda01f169..3639f83d38 100644 --- a/test/service/mirror_service_test.rb +++ b/test/service/mirror_service_test.rb @@ -8,7 +8,7 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase end.to_h config = mirror_config.merge \ - mirror: { service: "Mirror", primary: 'primary', mirrors: mirror_config.keys }, + mirror: { service: "Mirror", primary: "primary", mirrors: mirror_config.keys }, primary: { service: "Disk", root: Dir.mktmpdir("active_storage_tests_primary") } SERVICE = ActiveStorage::Service.configure :mirror, config From c49c56b46986c4a1b0701f9f4e16fe60222cf0fc Mon Sep 17 00:00:00 2001 From: dixpac Date: Fri, 14 Jul 2017 20:46:02 +0200 Subject: [PATCH 166/289] Revert back to the original implementaion with varaibles Revert `exist? and url` to the original implementation. Since the new one doesn't provide any benefits and makes implementation harder to follow. --- lib/active_storage/service/gcs_service.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index 1addda6733..b0ad3e2fa4 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -36,14 +36,20 @@ def delete(key) def exist?(key) instrument :exist, key do |payload| - payload[:exist] = file_for(key).present? + answer = file_for(key).present? + payload[:exist] = answer + answer end end def url(key, expires_in:, disposition:, filename:) instrument :url, key do |payload| query = { "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"" } - payload[:url] = file_for(key).signed_url(expires: expires_in, query: query) + generated_url = file_for(key).signed_url(expires: expires_in, query: query) + + payload[:url] = generated_url + + generated_url end end From 62e5562edb72f591c6af4c4da2bce3db18b5ba36 Mon Sep 17 00:00:00 2001 From: colorfulfool Date: Sat, 15 Jul 2017 00:25:07 +0300 Subject: [PATCH 167/289] Don't fail on boot because of missing S3 keys when S3 is not used --- lib/active_storage/storage_services.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/storage_services.yml b/lib/active_storage/storage_services.yml index a93304d88f..c80a3e8453 100644 --- a/lib/active_storage/storage_services.yml +++ b/lib/active_storage/storage_services.yml @@ -9,8 +9,8 @@ local: # Use rails secrets:edit to set the AWS secrets (as shared:aws:access_key_id|secret_access_key) amazon: service: S3 - access_key_id: <%= Rails.application.secrets.aws[:access_key_id] %> - secret_access_key: <%= Rails.application.secrets.aws[:secret_access_key] %> + access_key_id: <%= Rails.application.secrets.dig(:aws, :access_key_id) %> + secret_access_key: <%= Rails.application.secrets.dig(:aws, :secret_access_key) %> region: us-east-1 bucket: your_own_bucket From ed977c32e07a7bac82f08e66162e4ce95769d263 Mon Sep 17 00:00:00 2001 From: colorfulfool Date: Sat, 15 Jul 2017 00:25:43 +0300 Subject: [PATCH 168/289] Fail early if no storage service is specified --- lib/active_storage/engine.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index c251f522c6..5f0b62809e 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -56,6 +56,9 @@ class Engine < Rails::Engine # :nodoc: rescue => e raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace end + else + raise "No storage service specified for current env (#{Rails.env}). " \ + "Add config.active_storage.service = :local into your config/environments/#{Rails.env}.rb." end end end From a6df4515522c4b977a7739b7eab3fc54d9098a45 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Fri, 14 Jul 2017 18:26:27 -0400 Subject: [PATCH 169/289] Depend on Rails >= 5.2.0.alpha --- Gemfile | 7 +++++ Gemfile.lock | 67 ++++++++++++++++++++++++------------------- activestorage.gemspec | 12 ++++---- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/Gemfile b/Gemfile index d2d6db9065..7154892086 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,14 @@ source "https://rubygems.org" +git_source(:github) { |repo_path| "https://github.com/#{repo_path}.git" } + gemspec +gem "activesupport", github: "rails/rails" +gem "activerecord", github: "rails/rails" +gem "actionpack", github: "rails/rails" +gem "activejob", github: "rails/rails" + gem "rake" gem "byebug" diff --git a/Gemfile.lock b/Gemfile.lock index cce1d346da..eb8c229a41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,42 +1,47 @@ -PATH - remote: . +GIT + remote: https://github.com/rails/rails.git + revision: c1f9fa8c69590222fac43d58bf64ef4a1225d7ce specs: - activestorage (0.1) - actionpack (>= 5.1) - activejob (>= 5.1) - activerecord (>= 5.1) - activesupport (>= 5.1) - -GEM - remote: https://rubygems.org/ - specs: - actionpack (5.1.1) - actionview (= 5.1.1) - activesupport (= 5.1.1) + actionpack (5.2.0.alpha) + actionview (= 5.2.0.alpha) + activesupport (= 5.2.0.alpha) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.1) - activesupport (= 5.1.1) + actionview (5.2.0.alpha) + activesupport (= 5.2.0.alpha) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.1.1) - activesupport (= 5.1.1) + activejob (5.2.0.alpha) + activesupport (= 5.2.0.alpha) globalid (>= 0.3.6) - activemodel (5.1.1) - activesupport (= 5.1.1) - activerecord (5.1.1) - activemodel (= 5.1.1) - activesupport (= 5.1.1) + activemodel (5.2.0.alpha) + activesupport (= 5.2.0.alpha) + activerecord (5.2.0.alpha) + activemodel (= 5.2.0.alpha) + activesupport (= 5.2.0.alpha) arel (~> 8.0) - activesupport (5.1.1) + activesupport (5.2.0.alpha) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) + +PATH + remote: . + specs: + activestorage (0.1) + actionpack (>= 5.2.0.alpha) + activejob (>= 5.2.0.alpha) + activerecord (>= 5.2.0.alpha) + activesupport (>= 5.2.0.alpha) + +GEM + remote: https://rubygems.org/ + specs: addressable (2.5.1) public_suffix (~> 2.0, >= 2.0.2) arel (8.0.0) @@ -55,7 +60,7 @@ GEM declarative (0.0.9) declarative-option (0.1.0) digest-crc (0.4.1) - erubi (1.6.0) + erubi (1.6.1) faraday (0.12.1) multipart-post (>= 1.2, < 3) globalid (0.4.0) @@ -87,7 +92,7 @@ GEM httparty (0.15.5) multi_xml (>= 0.5.2) httpclient (2.8.3) - i18n (0.8.4) + i18n (0.8.6) jmespath (1.3.1) jwt (1.5.6) little-plugger (1.1.4) @@ -100,13 +105,13 @@ GEM mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) - mini_portile2 (2.1.0) + mini_portile2 (2.2.0) minitest (5.10.2) multi_json (1.12.1) multi_xml (0.6.0) multipart-post (2.0.0) - nokogiri (1.7.2) - mini_portile2 (~> 2.1.0) + nokogiri (1.8.0) + mini_portile2 (~> 2.2.0) os (0.9.6) parallel (1.11.2) parser (2.4.0.0) @@ -153,7 +158,11 @@ PLATFORMS ruby DEPENDENCIES + actionpack! + activejob! + activerecord! activestorage! + activesupport! aws-sdk (~> 2) bundler (~> 1.15) byebug diff --git a/activestorage.gemspec b/activestorage.gemspec index ce366d60c2..884d3287e6 100644 --- a/activestorage.gemspec +++ b/activestorage.gemspec @@ -9,13 +9,13 @@ s.required_ruby_version = ">= 2.3.0" - s.add_dependency "activesupport", ">= 5.1" - s.add_dependency "activerecord", ">= 5.1" - s.add_dependency "actionpack", ">= 5.1" - s.add_dependency "activejob", ">= 5.1" + s.add_dependency "activesupport", ">= 5.2.0.alpha" + s.add_dependency "activerecord", ">= 5.2.0.alpha" + s.add_dependency "actionpack", ">= 5.2.0.alpha" + s.add_dependency "activejob", ">= 5.2.0.alpha" s.add_development_dependency "bundler", "~> 1.15" - s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- test/*`.split("\n") + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- test/*`.split("\n") end From 315bd5c4477a0f7695c924b5266adcf27a5ae3fc Mon Sep 17 00:00:00 2001 From: Stanislav Gospodinov Date: Sat, 15 Jul 2017 05:15:12 +0300 Subject: [PATCH 170/289] Fixing logger to work with Rails 5.2.0.alpha --- lib/active_storage/log_subscriber.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/active_storage/log_subscriber.rb b/lib/active_storage/log_subscriber.rb index 5c486b9161..0d5f497403 100644 --- a/lib/active_storage/log_subscriber.rb +++ b/lib/active_storage/log_subscriber.rb @@ -2,9 +2,9 @@ class ActiveStorage::LogSubscriber < ActiveSupport::LogSubscriber def service_upload(event) - message = color("Uploaded file to key: #{key_in(event)}", GREEN) - message << color(" (checksum: #{event.payload[:checksum]})", GREEN) if event.payload[:checksum] - info event, message + message = "Uploaded file to key: #{key_in(event)}" + message << " (checksum: #{event.payload[:checksum]})" + info event, color(message, GREEN) if event.payload[:checksum] end def service_download(event) From a4e0e16e05fa49fc84b92a5ffd42a9f728ee1f89 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Fri, 14 Jul 2017 22:35:20 -0400 Subject: [PATCH 171/289] Put conditional back --- lib/active_storage/log_subscriber.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/log_subscriber.rb b/lib/active_storage/log_subscriber.rb index 0d5f497403..4ac34a3b25 100644 --- a/lib/active_storage/log_subscriber.rb +++ b/lib/active_storage/log_subscriber.rb @@ -3,8 +3,8 @@ class ActiveStorage::LogSubscriber < ActiveSupport::LogSubscriber def service_upload(event) message = "Uploaded file to key: #{key_in(event)}" - message << " (checksum: #{event.payload[:checksum]})" - info event, color(message, GREEN) if event.payload[:checksum] + message << " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum] + info event, color(message, GREEN) end def service_download(event) From 4dd44bc5e894d5a6e7f40881f318fa67e9aa1a77 Mon Sep 17 00:00:00 2001 From: Sean Handley Date: Fri, 14 Jul 2017 09:28:34 +0100 Subject: [PATCH 172/289] Allow custom endpoints for S3. --- lib/active_storage/service/s3_service.rb | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index e75ac36c7d..f78e2d3246 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -4,8 +4,24 @@ class ActiveStorage::Service::S3Service < ActiveStorage::Service attr_reader :client, :bucket - def initialize(access_key_id:, secret_access_key:, region:, bucket:) - @client = Aws::S3::Resource.new(access_key_id: access_key_id, secret_access_key: secret_access_key, region: region) + def initialize(access_key_id:, secret_access_key:, region:, bucket:, endpoint: nil) + @client = if endpoint + Aws::S3::Resource.new( + access_key_id: access_key_id, + secret_access_key: secret_access_key, + region: region, + bucket: bucket + ) + else + Aws::S3::Resource.new( + access_key_id: access_key_id, + secret_access_key: secret_access_key, + region: region, + bucket: bucket, + endpoint: endpoint + ) + end + @bucket = @client.bucket(bucket) end From 6074771f7926d4c93536d15d91de46fb549b329a Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Sat, 15 Jul 2017 08:42:10 -0400 Subject: [PATCH 173/289] Swap branches --- lib/active_storage/service/s3_service.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index f78e2d3246..4efc34eb80 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -10,15 +10,15 @@ def initialize(access_key_id:, secret_access_key:, region:, bucket:, endpoint: n access_key_id: access_key_id, secret_access_key: secret_access_key, region: region, - bucket: bucket + bucket: bucket, + endpoint: endpoint ) else Aws::S3::Resource.new( access_key_id: access_key_id, secret_access_key: secret_access_key, region: region, - bucket: bucket, - endpoint: endpoint + bucket: bucket ) end From 993283a1ae9e9c7f07c78c2ac8372a6e91228cc2 Mon Sep 17 00:00:00 2001 From: colorfulfool Date: Sun, 16 Jul 2017 01:06:39 +0300 Subject: [PATCH 174/289] Fix a typo in S3Service --- lib/active_storage/service/s3_service.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 4efc34eb80..ba8251b77e 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -10,15 +10,13 @@ def initialize(access_key_id:, secret_access_key:, region:, bucket:, endpoint: n access_key_id: access_key_id, secret_access_key: secret_access_key, region: region, - bucket: bucket, endpoint: endpoint ) else Aws::S3::Resource.new( access_key_id: access_key_id, secret_access_key: secret_access_key, - region: region, - bucket: bucket + region: region ) end From 2f15938587a6a3fa1ce6745511aeb81d833e51c2 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Sun, 16 Jul 2017 19:17:18 -0400 Subject: [PATCH 175/289] Fix S3 direct upload test --- test/service/s3_service_test.rb | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index 4875ac908b..81eff2c02d 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -9,25 +9,15 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase include ActiveStorage::Service::SharedServiceTests test "direct upload" do - # FIXME: This test is failing because of a mismatched request signature, but it works in the browser. - skip - begin key = SecureRandom.base58(24) data = "Something else entirely!" - direct_upload_url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size) + url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size) - url = URI.parse(direct_upload_url).to_s.split("?").first - query = CGI::parse(URI.parse(direct_upload_url).query).collect { |(k, v)| [ k, v.first ] }.to_h - - HTTParty.post( + HTTParty.put( url, - query: query, body: data, - headers: { - "Content-Type": "text/plain", - "Origin": "http://localhost:3000" - }, + headers: { "Content-Type" => "text/plain" }, debug_output: STDOUT ) From 94a450acbec0a33f1ad9003e6e9c5545549a3ab9 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Sun, 16 Jul 2017 19:17:47 -0400 Subject: [PATCH 176/289] Splat options --- lib/active_storage/service/s3_service.rb | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index ba8251b77e..5703cfd0ed 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -4,22 +4,8 @@ class ActiveStorage::Service::S3Service < ActiveStorage::Service attr_reader :client, :bucket - def initialize(access_key_id:, secret_access_key:, region:, bucket:, endpoint: nil) - @client = if endpoint - Aws::S3::Resource.new( - access_key_id: access_key_id, - secret_access_key: secret_access_key, - region: region, - endpoint: endpoint - ) - else - Aws::S3::Resource.new( - access_key_id: access_key_id, - secret_access_key: secret_access_key, - region: region - ) - end - + def initialize(access_key_id:, secret_access_key:, region:, bucket:, **options) + @client = Aws::S3::Resource.new(access_key_id: access_key_id, secret_access_key: secret_access_key, region: region, **options) @bucket = @client.bucket(bucket) end From be526d16fe29cf2c6c75a0c10b355271a87527d7 Mon Sep 17 00:00:00 2001 From: Michael Herold Date: Mon, 17 Jul 2017 08:17:09 -0500 Subject: [PATCH 177/289] Add direct upload support to GCS service --- lib/active_storage/service/gcs_service.rb | 11 ++++++++ test/direct_uploads_controller_test.rb | 34 +++++++++++++++++++++-- test/service/gcs_service_test.rb | 20 +++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/service/gcs_service.rb b/lib/active_storage/service/gcs_service.rb index b0ad3e2fa4..7053a130c0 100644 --- a/lib/active_storage/service/gcs_service.rb +++ b/lib/active_storage/service/gcs_service.rb @@ -53,6 +53,17 @@ def url(key, expires_in:, disposition:, filename:) end end + def url_for_direct_upload(key, expires_in:, content_type:, content_length:) + instrument :url, key do |payload| + generated_url = bucket.signed_url key, method: "PUT", expires: expires_in, + content_type: content_type + + payload[:url] = generated_url + + generated_url + end + end + private def file_for(key) bucket.file(key) diff --git a/test/direct_uploads_controller_test.rb b/test/direct_uploads_controller_test.rb index bed985148e..f96a37f758 100644 --- a/test/direct_uploads_controller_test.rb +++ b/test/direct_uploads_controller_test.rb @@ -7,7 +7,7 @@ require "active_storage/direct_uploads_controller" if SERVICE_CONFIGURATIONS[:s3] - class ActiveStorage::DirectUploadsControllerTest < ActionController::TestCase + class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase setup do @blob = create_blob @routes = Routes @@ -32,5 +32,35 @@ class ActiveStorage::DirectUploadsControllerTest < ActionController::TestCase end end else - puts "Skipping Direct Upload tests because no S3 configuration was supplied" + puts "Skipping S3 Direct Upload tests because no S3 configuration was supplied" +end + +if SERVICE_CONFIGURATIONS[:gcs] + class ActiveStorage::GCSDirectUploadsControllerTest < ActionController::TestCase + setup do + @blob = create_blob + @routes = Routes + @controller = ActiveStorage::DirectUploadsController.new + @config = SERVICE_CONFIGURATIONS[:gcs] + + @old_service = ActiveStorage::Blob.service + ActiveStorage::Blob.service = ActiveStorage::Service.configure(:gcs, SERVICE_CONFIGURATIONS) + end + + teardown do + ActiveStorage::Blob.service = @old_service + end + + test "creating new direct upload" do + post :create, params: { blob: { + filename: "hello.txt", byte_size: 6, checksum: Digest::MD5.base64digest("Hello"), content_type: "text/plain" } } + + details = JSON.parse(@response.body) + + assert_match %r{storage\.googleapis\.com/#{@config[:bucket]}}, details["url"] + assert_equal "hello.txt", GlobalID::Locator.locate_signed(details["sgid"]).filename.to_s + end + end +else + puts "Skipping GCS Direct Upload tests because no GCS configuration was supplied" end diff --git a/test/service/gcs_service_test.rb b/test/service/gcs_service_test.rb index 7d4700498b..3d70080af4 100644 --- a/test/service/gcs_service_test.rb +++ b/test/service/gcs_service_test.rb @@ -1,4 +1,5 @@ require "service/shared_service_tests" +require "httparty" if SERVICE_CONFIGURATIONS[:gcs] class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase @@ -6,6 +7,25 @@ class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase include ActiveStorage::Service::SharedServiceTests + test "direct upload" do + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + direct_upload_url = @service.url_for_direct_upload(key, expires_in: 5.minutes, content_type: "text/plain", content_length: data.size) + + HTTParty.put( + direct_upload_url, + body: data, + headers: { "Content-Type" => "text/plain" }, + debug_output: STDOUT + ) + + assert_equal data, @service.download(key) + ensure + @service.delete key + end + end + test "signed URL generation" do travel_to Time.now do url = SERVICE.bucket.signed_url(FIXTURE_KEY, expires: 120) + From bb7b8348e6f307fb9b7279bbab4ea19e9313a45e Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Mon, 17 Jul 2017 09:19:14 -0400 Subject: [PATCH 178/289] Remove unused require --- test/service/s3_service_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index 81eff2c02d..d823e1fdca 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -1,6 +1,5 @@ require "service/shared_service_tests" require "httparty" -require "uri" if SERVICE_CONFIGURATIONS[:s3] class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase From c8ad7dc13bbdc7b5c3305d5fd0a93f47d0f4baa4 Mon Sep 17 00:00:00 2001 From: "James T. Perreault" Date: Mon, 17 Jul 2017 10:17:33 -0400 Subject: [PATCH 179/289] Replace hard-coded S3 bucket name with configured bucket --- test/direct_uploads_controller_test.rb | 2 +- test/service/s3_service_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/direct_uploads_controller_test.rb b/test/direct_uploads_controller_test.rb index f96a37f758..8aa61f53cb 100644 --- a/test/direct_uploads_controller_test.rb +++ b/test/direct_uploads_controller_test.rb @@ -27,7 +27,7 @@ class ActiveStorage::S3DirectUploadsControllerTest < ActionController::TestCase details = JSON.parse(@response.body) - assert_match /rails-activestorage\.s3.amazonaws\.com/, details["url"] + assert_match /#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws\.com/, details["url"] assert_equal "hello.txt", GlobalID::Locator.locate_signed(details["sgid"]).filename.to_s end end diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index d823e1fdca..6115cb8db0 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -27,7 +27,7 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase end test "signed URL generation" do - assert_match /.+s3.+amazonaws.com.*response-content-disposition=inline.*avatar\.png/, + assert_match /#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png/, @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") end end From d0d4e33b86369e4b7c5656dd5db60e04f8c4d76e Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Wed, 19 Jul 2017 13:58:23 -0400 Subject: [PATCH 180/289] Use descriptive new freeze_time helper --- Gemfile.lock | 2 +- test/blob_test.rb | 2 +- test/service/gcs_service_test.rb | 2 +- test/service/mirror_service_test.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index eb8c229a41..5b283272f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/rails/rails.git - revision: c1f9fa8c69590222fac43d58bf64ef4a1225d7ce + revision: 5c16dd35a23f75038baf1527143ee44accf081ff specs: actionpack (5.2.0.alpha) actionview (= 5.2.0.alpha) diff --git a/test/blob_test.rb b/test/blob_test.rb index 6a9765a859..ddc000ed51 100644 --- a/test/blob_test.rb +++ b/test/blob_test.rb @@ -28,7 +28,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase test "urls expiring in 5 minutes" do blob = create_blob - travel_to Time.now do + freeze_time do assert_equal expected_url_for(blob), blob.url assert_equal expected_url_for(blob, disposition: :attachment), blob.url(disposition: :attachment) end diff --git a/test/service/gcs_service_test.rb b/test/service/gcs_service_test.rb index 3d70080af4..4cde4b9289 100644 --- a/test/service/gcs_service_test.rb +++ b/test/service/gcs_service_test.rb @@ -27,7 +27,7 @@ class ActiveStorage::Service::GCSServiceTest < ActiveSupport::TestCase end test "signed URL generation" do - travel_to Time.now do + freeze_time do url = SERVICE.bucket.signed_url(FIXTURE_KEY, expires: 120) + "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" diff --git a/test/service/mirror_service_test.rb b/test/service/mirror_service_test.rb index 3639f83d38..fd3d8125d6 100644 --- a/test/service/mirror_service_test.rb +++ b/test/service/mirror_service_test.rb @@ -45,7 +45,7 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase end test "URL generation in primary service" do - travel_to Time.now do + freeze_time do assert_equal SERVICE.primary.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt"), @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt") end From 32331b19e1da8bdab3c9f6d1666ac2d3108e5042 Mon Sep 17 00:00:00 2001 From: James Baer Date: Thu, 20 Jul 2017 12:29:37 -0400 Subject: [PATCH 181/289] Accept S3 upload options (e.g. server_side_encryption) --- lib/active_storage/service/s3_service.rb | 8 +++++--- test/service/s3_service_test.rb | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/active_storage/service/s3_service.rb b/lib/active_storage/service/s3_service.rb index 5703cfd0ed..efffdec157 100644 --- a/lib/active_storage/service/s3_service.rb +++ b/lib/active_storage/service/s3_service.rb @@ -2,17 +2,19 @@ require "active_support/core_ext/numeric/bytes" class ActiveStorage::Service::S3Service < ActiveStorage::Service - attr_reader :client, :bucket + attr_reader :client, :bucket, :upload_options - def initialize(access_key_id:, secret_access_key:, region:, bucket:, **options) + def initialize(access_key_id:, secret_access_key:, region:, bucket:, upload: {}, **options) @client = Aws::S3::Resource.new(access_key_id: access_key_id, secret_access_key: secret_access_key, region: region, **options) @bucket = @client.bucket(bucket) + + @upload_options = upload end def upload(key, io, checksum: nil) instrument :upload, key, checksum: checksum do begin - object_for(key).put(body: io, content_md5: checksum) + object_for(key).put(upload_options.merge(body: io, content_md5: checksum)) rescue Aws::S3::Errors::BadDigest raise ActiveStorage::IntegrityError end diff --git a/test/service/s3_service_test.rb b/test/service/s3_service_test.rb index 6115cb8db0..049511497b 100644 --- a/test/service/s3_service_test.rb +++ b/test/service/s3_service_test.rb @@ -30,6 +30,24 @@ class ActiveStorage::Service::S3ServiceTest < ActiveSupport::TestCase assert_match /#{SERVICE_CONFIGURATIONS[:s3][:bucket]}\.s3.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png/, @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png") end + + test "uploading with server-side encryption" do + config = {} + config[:s3] = SERVICE_CONFIGURATIONS[:s3].merge \ + upload: { server_side_encryption: "AES256" } + + sse_service = ActiveStorage::Service.configure(:s3, config) + + begin + key = SecureRandom.base58(24) + data = "Something else entirely!" + sse_service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data)) + + assert_equal "AES256", sse_service.bucket.object(key).server_side_encryption + ensure + sse_service.delete key + end + end end else puts "Skipping S3 Service tests because no S3 configuration was supplied" From 710957b20a24d0c62219cb7cc229c52905d74b3d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:04:54 -0500 Subject: [PATCH 182/289] Double confetti --- Gemfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Gemfile b/Gemfile index adc1e320e2..953b85ccfe 100644 --- a/Gemfile +++ b/Gemfile @@ -18,8 +18,6 @@ gem "httparty" gem "aws-sdk", "~> 2", require: false gem "google-cloud-storage", "~> 1.3", require: false -gem 'aws-sdk', '~> 2', require: false -gem 'google-cloud-storage', require: false gem 'mini_magick' gem "rubocop", require: false From 66d94ed78dced24cdd186e8bb5877cc6d43f5da8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:05:03 -0500 Subject: [PATCH 183/289] Easier access to the variant of a blob --- lib/active_storage/blob.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb index 1a15361747..a9d9b8771c 100644 --- a/lib/active_storage/blob.rb +++ b/lib/active_storage/blob.rb @@ -1,6 +1,7 @@ require "active_storage/service" require "active_storage/filename" require "active_storage/purge_job" +require "active_storage/variant" # Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at class ActiveStorage::Blob < ActiveRecord::Base @@ -40,6 +41,10 @@ def filename ActiveStorage::Filename.new(self[:filename]) end + def variant(variation) + ActiveStorage::Variant.new(self, variation: variation) + end + def url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename end From 5dbe5eaeb84b470624f1a2785315ddd4f7b1a4e3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:05:18 -0500 Subject: [PATCH 184/289] Follow AR like naming of factory method --- lib/active_storage/controllers/variants_controller.rb | 2 +- lib/active_storage/variant.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/controllers/variants_controller.rb b/lib/active_storage/controllers/variants_controller.rb index 24cee16e80..094f94e706 100644 --- a/lib/active_storage/controllers/variants_controller.rb +++ b/lib/active_storage/controllers/variants_controller.rb @@ -4,7 +4,7 @@ class ActiveStorage::Controllers::VariantsController < ActionController::Base def show if blob_key = decode_verified_key - variant = ActiveStorage::Variant.lookup(blob_key: blob_key, variation_key: params[:variation_key]) + variant = ActiveStorage::Variant.find_or_create_by(blob_key: blob_key, variation_key: params[:variation_key]) redirect_to variant.url else head :not_found diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 9b9dad43da..f005454b00 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -7,7 +7,7 @@ class ActiveStorage::Variant attr_reader :blob, :variation delegate :service, to: :blob - def self.lookup(blob_key:, variation_key:) + def self.find_or_create_by(blob_key:, variation_key:) new ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(variation_key) end From 76395e3c1b997da7b3853b1b3e94b712b1a29ecf Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:05:40 -0500 Subject: [PATCH 185/289] Do real transformations in a safe way --- lib/active_storage/variant.rb | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index f005454b00..62262c7790 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -1,9 +1,15 @@ require "active_storage/blob" +require "active_support/core_ext/object/inclusion" require "mini_magick" class ActiveStorage::Variant class_attribute :verifier + ALLOWED_TRANSFORMATIONS = %i( + resize rotate format flip fill monochrome orient quality roll scale sharpen shave shear size thumbnail + transparent transpose transverse trim background bordercolor compress crop + ) + attr_reader :blob, :variation delegate :service, to: :blob @@ -42,11 +48,21 @@ def upload_variant(variation) end def transform(io) - # FIXME: Actually do a variant based on the variation - File.open MiniMagick::Image.read(io).resize("500x500").path + File.open \ + MiniMagick::Image.read(io).tap { |transforming_image| + variation.each do |(method, argument)| + if method = allowed_transformational_method(method.to_sym) + if argument.present? + transforming_image.public_send(method, argument) + else + transforming_image.public_send(method) + end + end + end + }.path end - def exist? - service.exist?(key) + def allowed_transformational_method(method) + method.presence_in(ALLOWED_TRANSFORMATIONS) end end From f1523ab39e38bdc031c3bfb61ed4b6decd23ffcd Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:06:00 -0500 Subject: [PATCH 186/289] Use a unique blob variant key for storage --- lib/active_storage/variant.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 62262c7790..3053f44211 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -27,7 +27,7 @@ def initialize(blob, variation:) def url(expires_in: 5.minutes, disposition: :inline) perform unless exist? - service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename + service.url blob_variant_key, expires_in: expires_in, disposition: disposition, filename: blob.filename end def key @@ -35,6 +35,10 @@ def key end private + def exist? + service.exist?(blob_variant_key) + end + def perform upload_variant transform(download_blob) end @@ -44,7 +48,11 @@ def download_blob end def upload_variant(variation) - service.upload key, variation + service.upload blob_variant_key, variation + end + + def blob_variant_key + "variants/#{blob.key}/#{key}" end def transform(io) From dda013050fe559e2e9432ae836c1239eac48fbce Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:06:08 -0500 Subject: [PATCH 187/289] Use the direct accessor --- test/variation_test.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/variation_test.rb b/test/variation_test.rb index 3b05095292..bc47244468 100644 --- a/test/variation_test.rb +++ b/test/variation_test.rb @@ -7,9 +7,7 @@ class ActiveStorage::VariationTest < ActiveSupport::TestCase blob = ActiveStorage::Blob.create_after_upload! \ io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)), filename: "racecar.jpg", content_type: "image/jpeg" - variation_key = ActiveStorage::Variant.encode_key(resize: "500x500") - - variant = ActiveStorage::Variant.lookup(blob_key: blob.key, variation_key: variation_key) + variant = blob.variant(resize: "100x100") assert_match /racecar.jpg/, variant.url end From 1a9026b485b9b1da0f34c526d4c901406074c508 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:33:31 -0500 Subject: [PATCH 188/289] Extract routes.rb to engine location for auto configuration --- config/routes.rb | 13 +++++++++++++ lib/active_storage/engine.rb | 11 ----------- lib/active_storage/routes.rb | 3 --- test/test_helper.rb | 11 ----------- 4 files changed, 13 insertions(+), 25 deletions(-) create mode 100644 config/routes.rb delete mode 100644 lib/active_storage/routes.rb diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000000..9057eadc8a --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,13 @@ +Rails.application.routes.draw do + get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob + get "/rails/active_storage/variants/:encoded_blob_key/:encoded_variant_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant + post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads + + resolve 'ActiveStorage::Variant' do |variant| + encoded_blob_key = ActiveStorage::VerifiedKeyWithExpiration.encode(variant.blob.key) + encoded_variant_key = ActiveStorage::Variant.encode_key(variant.variation) + filename = variant.blob.filename + + route_for(:rails_blob_variant, encoded_blob_key, encoded_variant_key, filename) + end +end diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index 8918b179e0..b04925a9fd 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -14,17 +14,6 @@ class Engine < Rails::Engine # :nodoc: end end - initializer "active_storage.routes" do - require "active_storage/disk_controller" - require "active_storage/direct_uploads_controller" - - config.after_initialize do |app| - app.routes.prepend do - eval(File.read(File.expand_path("../routes.rb", __FILE__))) - end - end - end - initializer "active_storage.attached" do require "active_storage/attached" diff --git a/lib/active_storage/routes.rb b/lib/active_storage/routes.rb deleted file mode 100644 index fade234ad3..0000000000 --- a/lib/active_storage/routes.rb +++ /dev/null @@ -1,3 +0,0 @@ -get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob -get "/rails/active_storage/variants/:encoded_key/:encoded_transformation/*filename" => "active_storage/controllers/variants#show", as: :rails_blob_variant -post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads diff --git a/test/test_helper.rb b/test/test_helper.rb index 878ce8391c..6081fc1bcf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -34,17 +34,6 @@ def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text end end -require "action_controller" -require "action_controller/test_case" - -class ActionController::TestCase - Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| - routes.draw do - eval(File.read(File.expand_path("../../lib/active_storage/routes.rb", __FILE__))) - end - end -end - require "active_storage/attached" ActiveRecord::Base.send :extend, ActiveStorage::Attached::Macros From 1c85eecee02ebf0c0148de807fbb1a9e9573af8a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:34:13 -0500 Subject: [PATCH 189/289] Move controllers to default engine location for auto loading --- Rakefile | 1 + .../active_storage/disk_controller.rb | 0 .../active_storage/variants_controller.rb | 22 +++++++++++++++++++ .../direct_uploads_controller.rb | 14 ------------ 4 files changed, 23 insertions(+), 14 deletions(-) rename {lib => app/controllers}/active_storage/disk_controller.rb (100%) create mode 100644 app/controllers/active_storage/variants_controller.rb delete mode 100644 lib/active_storage/direct_uploads_controller.rb diff --git a/Rakefile b/Rakefile index f0baf50163..a41e07f373 100644 --- a/Rakefile +++ b/Rakefile @@ -3,6 +3,7 @@ require "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new do |test| + test.libs << "app/controllers" test.libs << "test" test.test_files = FileList["test/**/*_test.rb"] test.warning = false diff --git a/lib/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb similarity index 100% rename from lib/active_storage/disk_controller.rb rename to app/controllers/active_storage/disk_controller.rb diff --git a/app/controllers/active_storage/variants_controller.rb b/app/controllers/active_storage/variants_controller.rb new file mode 100644 index 0000000000..05685dca17 --- /dev/null +++ b/app/controllers/active_storage/variants_controller.rb @@ -0,0 +1,22 @@ +class ActiveStorage::VariantsController < ActionController::Base + def show + if blob_key = decode_verified_blob_key + redirect_to processed_variant_for(blob_key).url(disposition: disposition_param) + else + head :not_found + end + end + + private + def decode_verified_blob_key + ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_blob_key]) + end + + def processed_variant_for(blob_key) + ActiveStorage::Variant.find_or_process_by!(blob_key: blob_key, encoded_variant_key: params[:encoded_variant_key]) + end + + def disposition_param + params[:disposition].presence_in(%w( inline attachment )) || 'inline' + end +end diff --git a/lib/active_storage/direct_uploads_controller.rb b/lib/active_storage/direct_uploads_controller.rb deleted file mode 100644 index 99ff27f903..0000000000 --- a/lib/active_storage/direct_uploads_controller.rb +++ /dev/null @@ -1,14 +0,0 @@ -require "action_controller" -require "active_storage/blob" - -class ActiveStorage::DirectUploadsController < ActionController::Base - def create - blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args) - render json: { url: blob.url_for_direct_upload, sgid: blob.to_sgid.to_param } - end - - private - def blob_args - params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys - end -end From 6c2cef21ce67f83bff45ce76c0370b03be11451f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:34:32 -0500 Subject: [PATCH 190/289] Fix-up variants controller --- .../direct_uploads_controller.rb | 11 ++++++++++ .../controllers/variants_controller.rb | 22 ------------------- 2 files changed, 11 insertions(+), 22 deletions(-) create mode 100644 app/controllers/active_storage/direct_uploads_controller.rb delete mode 100644 lib/active_storage/controllers/variants_controller.rb diff --git a/app/controllers/active_storage/direct_uploads_controller.rb b/app/controllers/active_storage/direct_uploads_controller.rb new file mode 100644 index 0000000000..dccd864e8d --- /dev/null +++ b/app/controllers/active_storage/direct_uploads_controller.rb @@ -0,0 +1,11 @@ +class ActiveStorage::DirectUploadsController < ActionController::Base + def create + blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args) + render json: { url: blob.url_for_direct_upload, sgid: blob.to_sgid.to_param } + end + + private + def blob_args + params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys + end +end diff --git a/lib/active_storage/controllers/variants_controller.rb b/lib/active_storage/controllers/variants_controller.rb deleted file mode 100644 index 094f94e706..0000000000 --- a/lib/active_storage/controllers/variants_controller.rb +++ /dev/null @@ -1,22 +0,0 @@ -require "action_controller" -require "active_storage/blob" - -class ActiveStorage::Controllers::VariantsController < ActionController::Base - def show - if blob_key = decode_verified_key - variant = ActiveStorage::Variant.find_or_create_by(blob_key: blob_key, variation_key: params[:variation_key]) - redirect_to variant.url - else - head :not_found - end - end - - private - def decode_verified_key - ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key]) - end - - def disposition_param - params[:disposition].presence_in(%w( inline attachment )) || 'inline' - end -end From af999681122bf583b6644974ba2033453935fd6d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:35:15 -0500 Subject: [PATCH 191/289] Make processing an explicit step --- lib/active_storage/variant.rb | 12 ++++++++---- test/variation_test.rb | 4 +--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 3053f44211..a07735eb57 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -13,8 +13,8 @@ class ActiveStorage::Variant attr_reader :blob, :variation delegate :service, to: :blob - def self.find_or_create_by(blob_key:, variation_key:) - new ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(variation_key) + def self.find_or_process_by!(blob_key:, encoded_variant_key:) + new(ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(encoded_variant_key)).processed end def self.encode_key(variation) @@ -25,8 +25,12 @@ def initialize(blob, variation:) @blob, @variation = blob, variation end + def processed + process unless exist? + self + end + def url(expires_in: 5.minutes, disposition: :inline) - perform unless exist? service.url blob_variant_key, expires_in: expires_in, disposition: disposition, filename: blob.filename end @@ -39,7 +43,7 @@ def exist? service.exist?(blob_variant_key) end - def perform + def process upload_variant transform(download_blob) end diff --git a/test/variation_test.rb b/test/variation_test.rb index bc47244468..8e569f908c 100644 --- a/test/variation_test.rb +++ b/test/variation_test.rb @@ -7,8 +7,6 @@ class ActiveStorage::VariationTest < ActiveSupport::TestCase blob = ActiveStorage::Blob.create_after_upload! \ io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)), filename: "racecar.jpg", content_type: "image/jpeg" - variant = blob.variant(resize: "100x100") - - assert_match /racecar.jpg/, variant.url + assert_match /racecar.jpg/, blob.variant(resize: "100x100").processed.url end end From a968e3c3c75df3f209275d31eb0bd4ed6effd51e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:35:23 -0500 Subject: [PATCH 192/289] Consistent naming --- lib/active_storage/variant.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index a07735eb57..658fb2f5bd 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -51,8 +51,8 @@ def download_blob service.download(blob.key) end - def upload_variant(variation) - service.upload blob_variant_key, variation + def upload_variant(variant) + service.upload blob_variant_key, variant end def blob_variant_key From beb60b9c3a3f1f51d10fa800b967402d79ffcf28 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:35:41 -0500 Subject: [PATCH 193/289] True is the same as no arguments --- lib/active_storage/variant.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 658fb2f5bd..7fcd3924f4 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -64,10 +64,11 @@ def transform(io) MiniMagick::Image.read(io).tap { |transforming_image| variation.each do |(method, argument)| if method = allowed_transformational_method(method.to_sym) - if argument.present? - transforming_image.public_send(method, argument) - else + if argument.blank? || argument == true transforming_image.public_send(method) + else + # FIXME: Consider whitelisting allowed arguments as well? + transforming_image.public_send(method, argument) end end end From 986a71d26868d296f4c619df85909d1073b6c91f Mon Sep 17 00:00:00 2001 From: Dino Maric Date: Fri, 21 Jul 2017 15:14:07 +0200 Subject: [PATCH 194/289] Add instruction to install the gem from the master (#65) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 625b960624..27678a8fe1 100644 --- a/README.md +++ b/README.md @@ -65,9 +65,10 @@ end ## Installation -1. Add `require "active_storage"` to config/application.rb, after `require "rails/all"` line. -2. Run `rails activestorage:install` to create needed directories, migrations, and configuration. -3. Configure the storage service in `config/environments/*` with `config.active_storage.service = :local` +1. Add `gem "activestorage", git: "https://github.com/rails/activestorage.git"` to your Gemfile. +2. Add `require "active_storage"` to config/application.rb, after `require "rails/all"` line. +3. Run `rails activestorage:install` to create needed directories, migrations, and configuration. +4. Configure the storage service in `config/environments/*` with `config.active_storage.service = :local` that references the services configured in `config/storage_services.yml`. ## Todos From cbe89319de0d2eda8ee53ab7c9d3bc92751deeb4 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 12:35:00 -0500 Subject: [PATCH 195/289] Better naming --- lib/active_storage/variant.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 7fcd3924f4..4145ee644d 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -26,7 +26,7 @@ def initialize(blob, variation:) end def processed - process unless exist? + process unless processed? self end @@ -38,8 +38,9 @@ def key verifier.generate(variation) end + private - def exist? + def processed? service.exist?(blob_variant_key) end From 438d5cc48e92345f000f51bc0780b543b3087846 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:49:48 -0500 Subject: [PATCH 196/289] Accept that this is a full-Rails engine --- Gemfile.lock | 50 ++++++++++++++++++++++++++++++++++++++----- activestorage.gemspec | 5 +---- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8fe836eb70..e18acab95b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,16 @@ GIT remote: https://github.com/rails/rails.git revision: 5c16dd35a23f75038baf1527143ee44accf081ff specs: + actioncable (5.2.0.alpha) + actionpack (= 5.2.0.alpha) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.2.0.alpha) + actionpack (= 5.2.0.alpha) + actionview (= 5.2.0.alpha) + activejob (= 5.2.0.alpha) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) actionpack (5.2.0.alpha) actionview (= 5.2.0.alpha) activesupport (= 5.2.0.alpha) @@ -29,15 +39,30 @@ GIT i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) + rails (5.2.0.alpha) + actioncable (= 5.2.0.alpha) + actionmailer (= 5.2.0.alpha) + actionpack (= 5.2.0.alpha) + actionview (= 5.2.0.alpha) + activejob (= 5.2.0.alpha) + activemodel (= 5.2.0.alpha) + activerecord (= 5.2.0.alpha) + activesupport (= 5.2.0.alpha) + bundler (>= 1.3.0) + railties (= 5.2.0.alpha) + sprockets-rails (>= 2.0.0) + railties (5.2.0.alpha) + actionpack (= 5.2.0.alpha) + activesupport (= 5.2.0.alpha) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) PATH remote: . specs: activestorage (0.1) - actionpack (>= 5.2.0.alpha) - activejob (>= 5.2.0.alpha) - activerecord (>= 5.2.0.alpha) - activesupport (>= 5.2.0.alpha) + rails (>= 5.2.0.alpha) GEM remote: https://rubygems.org/ @@ -101,7 +126,10 @@ GEM multi_json (~> 1.10) loofah (2.0.3) nokogiri (>= 1.5.9) + mail (2.6.5) + mime-types (>= 1.16, < 4) memoist (0.16.0) + method_source (0.8.2) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) @@ -111,6 +139,7 @@ GEM multi_json (1.12.1) multi_xml (0.6.0) multipart-post (2.0.0) + nio4r (2.1.0) nokogiri (1.8.0) mini_portile2 (~> 2.2.0) os (0.9.6) @@ -148,12 +177,23 @@ GEM faraday (~> 0.9) jwt (~> 1.5) multi_json (~> 1.10) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.0) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) sqlite3 (1.3.13) + thor (0.19.4) thread_safe (0.3.6) tzinfo (1.2.3) thread_safe (~> 0.1) uber (0.1.0) unicode-display_width (1.3.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) PLATFORMS ruby @@ -175,4 +215,4 @@ DEPENDENCIES sqlite3 BUNDLED WITH - 1.15.1 + 1.15.2 diff --git a/activestorage.gemspec b/activestorage.gemspec index 884d3287e6..9546b60783 100644 --- a/activestorage.gemspec +++ b/activestorage.gemspec @@ -9,10 +9,7 @@ s.required_ruby_version = ">= 2.3.0" - s.add_dependency "activesupport", ">= 5.2.0.alpha" - s.add_dependency "activerecord", ">= 5.2.0.alpha" - s.add_dependency "actionpack", ">= 5.2.0.alpha" - s.add_dependency "activejob", ">= 5.2.0.alpha" + s.add_dependency "rails", ">= 5.2.0.alpha" s.add_development_dependency "bundler", "~> 1.15" From 9ac31a3c8a7bf996ef2614a2dc83c1d345c78b35 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:50:19 -0500 Subject: [PATCH 197/289] Mount routes on the engine --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 9057eadc8a..ad54b178fe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,4 @@ -Rails.application.routes.draw do +ActiveStorage::Engine.routes.draw do get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob get "/rails/active_storage/variants/:encoded_blob_key/:encoded_variant_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads From 0c47740d858cb04c7165bce411584c3b05d155b6 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:50:36 -0500 Subject: [PATCH 198/289] Hacky way to mount routes for engine controller tests --- test/test_helper.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index 6081fc1bcf..af379ad35a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -34,6 +34,17 @@ def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text end end +require "action_controller" +require "action_controller/test_case" +class ActionController::TestCase + Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| + routes.draw do + # FIXME: Hacky way to avoid having to instantiate the real engine + eval(File.readlines(File.expand_path("../../config/routes.rb", __FILE__)).slice(1..-2).join("\n")) + end + end +end + require "active_storage/attached" ActiveRecord::Base.send :extend, ActiveStorage::Attached::Macros From 7f4111185ca6286adbe0ffc1346d416c5fa7dfd3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:51:31 -0500 Subject: [PATCH 199/289] Extract variation value object --- lib/active_storage/blob.rb | 8 ++-- lib/active_storage/engine.rb | 2 +- lib/active_storage/variant.rb | 66 ++++++--------------------------- lib/active_storage/variation.rb | 53 ++++++++++++++++++++++++++ test/test_helper.rb | 5 +-- test/variation_test.rb | 15 ++++++-- 6 files changed, 84 insertions(+), 65 deletions(-) create mode 100644 lib/active_storage/variation.rb diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb index a9d9b8771c..6bd3941cd8 100644 --- a/lib/active_storage/blob.rb +++ b/lib/active_storage/blob.rb @@ -32,8 +32,9 @@ def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: end end - # We can't wait until the record is first saved to have a key for it + def key + # We can't wait until the record is first saved to have a key for it self[:key] ||= self.class.generate_unique_secure_token end @@ -41,10 +42,11 @@ def filename ActiveStorage::Filename.new(self[:filename]) end - def variant(variation) - ActiveStorage::Variant.new(self, variation: variation) + def variant(transformations) + ActiveStorage::Variant.new(self, ActiveStorage::Variation.new(transformations)) end + def url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename end diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index b04925a9fd..11227a4e04 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -28,7 +28,7 @@ class Engine < Rails::Engine # :nodoc: config.after_initialize do |app| ActiveStorage::VerifiedKeyWithExpiration.verifier = \ - ActiveStorage::Variant.verifier = \ + ActiveStorage::Variation.verifier = \ Rails.application.message_verifier('ActiveStorage') end end diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 4145ee644d..8be51eba92 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -1,82 +1,40 @@ require "active_storage/blob" -require "active_support/core_ext/object/inclusion" require "mini_magick" class ActiveStorage::Variant - class_attribute :verifier - - ALLOWED_TRANSFORMATIONS = %i( - resize rotate format flip fill monochrome orient quality roll scale sharpen shave shear size thumbnail - transparent transpose transverse trim background bordercolor compress crop - ) - attr_reader :blob, :variation delegate :service, to: :blob - def self.find_or_process_by!(blob_key:, encoded_variant_key:) - new(ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(encoded_variant_key)).processed + class << self + def find_or_process_by!(blob_key:, variation_key:) + new(ActiveStorage::Blob.find_by!(key: blob_key), variation: ActiveStorage::Variation.decode(variation_key)).processed + end end - def self.encode_key(variation) - verifier.generate(variation) - end - - def initialize(blob, variation:) + def initialize(blob, variation) @blob, @variation = blob, variation end def processed - process unless processed? + process unless service.exist?(key) self end - def url(expires_in: 5.minutes, disposition: :inline) - service.url blob_variant_key, expires_in: expires_in, disposition: disposition, filename: blob.filename + def key + "variants/#{blob.key}/#{variation.key}" end - def key - verifier.generate(variation) + def url(expires_in: 5.minutes, disposition: :inline) + service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename end private - def processed? - service.exist?(blob_variant_key) - end - def process - upload_variant transform(download_blob) - end - - def download_blob - service.download(blob.key) - end - - def upload_variant(variant) - service.upload blob_variant_key, variant - end - - def blob_variant_key - "variants/#{blob.key}/#{key}" + service.upload key, transform(service.download(blob.key)) end def transform(io) - File.open \ - MiniMagick::Image.read(io).tap { |transforming_image| - variation.each do |(method, argument)| - if method = allowed_transformational_method(method.to_sym) - if argument.blank? || argument == true - transforming_image.public_send(method) - else - # FIXME: Consider whitelisting allowed arguments as well? - transforming_image.public_send(method, argument) - end - end - end - }.path - end - - def allowed_transformational_method(method) - method.presence_in(ALLOWED_TRANSFORMATIONS) + File.open MiniMagick::Image.read(io).tap { |image| variation.transform(image) }.path end end diff --git a/lib/active_storage/variation.rb b/lib/active_storage/variation.rb new file mode 100644 index 0000000000..abff288ac1 --- /dev/null +++ b/lib/active_storage/variation.rb @@ -0,0 +1,53 @@ +require "active_support/core_ext/object/inclusion" + +# A set of transformations that can be applied to a blob to create a variant. +class ActiveStorage::Variation + class_attribute :verifier + + ALLOWED_TRANSFORMATIONS = %i( + resize rotate format flip fill monochrome orient quality roll scale sharpen shave shear size thumbnail + transparent transpose transverse trim background bordercolor compress crop + ) + + attr_reader :transformations + + class << self + def decode(key) + new verifier.verify(key) + end + + def encode(transformations) + verifier.generate(transformations) + end + end + + def initialize(transformations) + @transformations = transformations + end + + def transform(image) + transformations.each do |(method, argument)| + next unless eligible_transformation?(method) + + if eligible_argument?(argument) + image.public_send(method, argument) + else + image.public_send(method) + end + end + end + + def key + self.class.encode(transformations) + end + + private + def eligible_transformation?(method) + method.to_sym.in?(ALLOWED_TRANSFORMATIONS) + end + + # FIXME: Consider whitelisting allowed arguments as well? + def eligible_argument?(argument) + argument.present? && argument != true + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index af379ad35a..e98b6e0afc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -15,7 +15,6 @@ {} end - require "active_storage/service/disk_service" require "tmpdir" ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: Dir.mktmpdir("active_storage_tests")) @@ -24,8 +23,8 @@ require "active_storage/verified_key_with_expiration" ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") -require "active_storage/variant" -ActiveStorage::Variant.verifier = ActiveSupport::MessageVerifier.new("Testing") +require "active_storage/variation" +ActiveStorage::Variation.verifier = ActiveSupport::MessageVerifier.new("Testing") class ActiveSupport::TestCase private diff --git a/test/variation_test.rb b/test/variation_test.rb index 8e569f908c..d138682005 100644 --- a/test/variation_test.rb +++ b/test/variation_test.rb @@ -3,10 +3,17 @@ require "active_storage/variant" class ActiveStorage::VariationTest < ActiveSupport::TestCase - test "square variation" do - blob = ActiveStorage::Blob.create_after_upload! \ - io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)), filename: "racecar.jpg", content_type: "image/jpeg" + setup do + @blob = ActiveStorage::Blob.create_after_upload! \ + filename: "racecar.jpg", content_type: "image/jpeg", + io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) + end - assert_match /racecar.jpg/, blob.variant(resize: "100x100").processed.url + test "resized variation" do + assert_match /racecar.jpg/, @blob.variant(resize: "100x100").processed.url + end + + test "resized and monochrome variation" do + assert_match /racecar.jpg/, @blob.variant(resize: "100x100", monochrome: true).processed.url end end From c6952638818213a2a475437bc93e60b4386ac02a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:52:09 -0500 Subject: [PATCH 200/289] Precise naming --- test/{variation_test.rb => variant_test.rb} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/{variation_test.rb => variant_test.rb} (90%) diff --git a/test/variation_test.rb b/test/variant_test.rb similarity index 90% rename from test/variation_test.rb rename to test/variant_test.rb index d138682005..bc3a1fef90 100644 --- a/test/variation_test.rb +++ b/test/variant_test.rb @@ -2,7 +2,7 @@ require "database/setup" require "active_storage/variant" -class ActiveStorage::VariationTest < ActiveSupport::TestCase +class ActiveStorage::VariantTest < ActiveSupport::TestCase setup do @blob = ActiveStorage::Blob.create_after_upload! \ filename: "racecar.jpg", content_type: "image/jpeg", From 67606dcdf52ae7f83e42a9872fdc545b02f227a2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:52:21 -0500 Subject: [PATCH 201/289] Over-indented --- test/variant_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/variant_test.rb b/test/variant_test.rb index bc3a1fef90..ee3bc5c0e5 100644 --- a/test/variant_test.rb +++ b/test/variant_test.rb @@ -5,8 +5,8 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase setup do @blob = ActiveStorage::Blob.create_after_upload! \ - filename: "racecar.jpg", content_type: "image/jpeg", - io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) + filename: "racecar.jpg", content_type: "image/jpeg", + io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) end test "resized variation" do From 796f8330ad441e93590a57521ef8fb80a030fb66 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:12:29 -0500 Subject: [PATCH 202/289] Fix and test VariantsController --- .../active_storage/variants_controller.rb | 5 +++- config/routes.rb | 10 ++++---- lib/active_storage/variant.rb | 6 ----- test/controllers/variants_controller.rb | 25 +++++++++++++++++++ test/test_helper.rb | 2 ++ 5 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 test/controllers/variants_controller.rb diff --git a/app/controllers/active_storage/variants_controller.rb b/app/controllers/active_storage/variants_controller.rb index 05685dca17..dde7e1458f 100644 --- a/app/controllers/active_storage/variants_controller.rb +++ b/app/controllers/active_storage/variants_controller.rb @@ -13,7 +13,10 @@ def decode_verified_blob_key end def processed_variant_for(blob_key) - ActiveStorage::Variant.find_or_process_by!(blob_key: blob_key, encoded_variant_key: params[:encoded_variant_key]) + ActiveStorage::Variant.new( + ActiveStorage::Blob.find_by!(key: blob_key), + ActiveStorage::Variation.decode(params[:variation_key]) + ).processed end def disposition_param diff --git a/config/routes.rb b/config/routes.rb index ad54b178fe..bd0787180a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,13 +1,13 @@ ActiveStorage::Engine.routes.draw do get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob - get "/rails/active_storage/variants/:encoded_blob_key/:encoded_variant_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant + get "/rails/active_storage/variants/:encoded_blob_key/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads resolve 'ActiveStorage::Variant' do |variant| - encoded_blob_key = ActiveStorage::VerifiedKeyWithExpiration.encode(variant.blob.key) - encoded_variant_key = ActiveStorage::Variant.encode_key(variant.variation) - filename = variant.blob.filename + encoded_blob_key = ActiveStorage::VerifiedKeyWithExpiration.encode(variant.blob.key) + variantion_key = ActiveStorage::Variation.encode(variant.variation) + filename = variant.blob.filename - route_for(:rails_blob_variant, encoded_blob_key, encoded_variant_key, filename) + route_for(:rails_blob_variant, encoded_blob_key, variantion_key, filename) end end diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 8be51eba92..ba2604eccf 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -5,12 +5,6 @@ class ActiveStorage::Variant attr_reader :blob, :variation delegate :service, to: :blob - class << self - def find_or_process_by!(blob_key:, variation_key:) - new(ActiveStorage::Blob.find_by!(key: blob_key), variation: ActiveStorage::Variation.decode(variation_key)).processed - end - end - def initialize(blob, variation) @blob, @variation = blob, variation end diff --git a/test/controllers/variants_controller.rb b/test/controllers/variants_controller.rb new file mode 100644 index 0000000000..132d93a3cf --- /dev/null +++ b/test/controllers/variants_controller.rb @@ -0,0 +1,25 @@ +require "test_helper" +require "database/setup" + +require "active_storage/variants_controller" +require "active_storage/verified_key_with_expiration" + +class ActiveStorage::VariantsControllerTest < ActionController::TestCase + setup do + @blob = ActiveStorage::Blob.create_after_upload! \ + filename: "racecar.jpg", content_type: "image/jpeg", + io: File.open(File.expand_path("../../fixtures/files/racecar.jpg", __FILE__)) + + @routes = Routes + @controller = ActiveStorage::VariantsController.new + end + + test "showing variant inline" do + get :show, params: { + filename: @blob.filename, + encoded_blob_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), + variation_key: ActiveStorage::Variation.encode(resize: "100x100") } + + assert_redirected_to /racecar.jpg\?disposition=inline/ + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index e98b6e0afc..7aa7b50bf3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,3 +1,5 @@ +$LOAD_PATH << File.expand_path("../../app/controllers", __FILE__) + require "bundler/setup" require "active_support" require "active_support/test_case" From dd3eced57622f256891ce97cdd0cf1feabef40c2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:24:39 -0500 Subject: [PATCH 203/289] Proper require --- lib/active_storage/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index 11227a4e04..b32ae34516 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -24,7 +24,7 @@ class Engine < Rails::Engine # :nodoc: initializer "active_storage.verifiers" do require "active_storage/verified_key_with_expiration" - require "active_storage/variant" + require "active_storage/variation" config.after_initialize do |app| ActiveStorage::VerifiedKeyWithExpiration.verifier = \ From c231a73b892e1fd2d4ae2e939fe36bee0238f919 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:25:01 -0500 Subject: [PATCH 204/289] Provide directed URL as well as resolving --- config/routes.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index bd0787180a..c376493199 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,13 +1,16 @@ ActiveStorage::Engine.routes.draw do get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob - get "/rails/active_storage/variants/:encoded_blob_key/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads - resolve 'ActiveStorage::Variant' do |variant| + get "/rails/active_storage/variants/:encoded_blob_key/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation + + direct :rails_variant do |variant| encoded_blob_key = ActiveStorage::VerifiedKeyWithExpiration.encode(variant.blob.key) - variantion_key = ActiveStorage::Variation.encode(variant.variation) + variation_key = variant.variation.key filename = variant.blob.filename - route_for(:rails_blob_variant, encoded_blob_key, variantion_key, filename) + route_for(:rails_blob_variation, encoded_blob_key, variation_key, filename) end + + resolve 'ActiveStorage::Variant' { |variant| route_for(:rails_variant, variant) } end From 39f9ef122dc011d08e0b8d76cad0112c2fa3665f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:25:11 -0500 Subject: [PATCH 205/289] Actually we just want them mounted straight --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index c376493199..ec4d954e81 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,4 @@ -ActiveStorage::Engine.routes.draw do +Rails.application.routes.draw do get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads From b6fd579a7e97f1a3aee27d22e12784f7a6155799 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:26:34 -0500 Subject: [PATCH 206/289] Fix parens after inline block --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index ec4d954e81..d25f2c82f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,5 +12,5 @@ route_for(:rails_blob_variation, encoded_blob_key, variation_key, filename) end - resolve 'ActiveStorage::Variant' { |variant| route_for(:rails_variant, variant) } + resolve('ActiveStorage::Variant') { |variant| route_for(:rails_variant, variant) } end From 08d84e225cca6772aa54dfb7123120fe1070ea30 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:34:18 -0500 Subject: [PATCH 207/289] Extract test helper for image blob fixtures --- test/controllers/variants_controller.rb | 6 ++---- test/test_helper.rb | 6 ++++++ test/variant_test.rb | 4 +--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test/controllers/variants_controller.rb b/test/controllers/variants_controller.rb index 132d93a3cf..22f5ec1454 100644 --- a/test/controllers/variants_controller.rb +++ b/test/controllers/variants_controller.rb @@ -6,12 +6,10 @@ class ActiveStorage::VariantsControllerTest < ActionController::TestCase setup do - @blob = ActiveStorage::Blob.create_after_upload! \ - filename: "racecar.jpg", content_type: "image/jpeg", - io: File.open(File.expand_path("../../fixtures/files/racecar.jpg", __FILE__)) - @routes = Routes @controller = ActiveStorage::VariantsController.new + + @blob = create_image_blob filename: "racecar.jpg" end test "showing variant inline" do diff --git a/test/test_helper.rb b/test/test_helper.rb index 7aa7b50bf3..69ba76b9c4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -33,6 +33,12 @@ class ActiveSupport::TestCase def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type end + + def create_image_blob(filename: "racecar.jpg", content_type: "image/jpeg") + ActiveStorage::Blob.create_after_upload! \ + io: File.open(File.expand_path("../fixtures/files/#{filename}", __FILE__)), + filename: filename, content_type: content_type + end end require "action_controller" diff --git a/test/variant_test.rb b/test/variant_test.rb index ee3bc5c0e5..0368960fbf 100644 --- a/test/variant_test.rb +++ b/test/variant_test.rb @@ -4,9 +4,7 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase setup do - @blob = ActiveStorage::Blob.create_after_upload! \ - filename: "racecar.jpg", content_type: "image/jpeg", - io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) + @blob = create_image_blob filename: "racecar.jpg" end test "resized variation" do From fa33ec9e7decde0fc0ba3e2bd2b7fc9f06908065 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:34:28 -0500 Subject: [PATCH 208/289] Anemic intro --- lib/active_storage/variant.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index ba2604eccf..435033f980 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -1,6 +1,7 @@ require "active_storage/blob" require "mini_magick" +# Image blobs can have variants that are the result of a set of transformations applied to the original. class ActiveStorage::Variant attr_reader :blob, :variation delegate :service, to: :blob From e9cf92cc399a169ec47496da198cfb984856000d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:44:10 -0500 Subject: [PATCH 209/289] Test actual transformations --- .../files/racecar-100x100-monochrome.jpg | Bin 0 -> 27586 bytes test/fixtures/files/racecar-100x100.jpg | Bin 0 -> 29446 bytes test/variant_test.rb | 17 +++++++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/files/racecar-100x100-monochrome.jpg create mode 100644 test/fixtures/files/racecar-100x100.jpg diff --git a/test/fixtures/files/racecar-100x100-monochrome.jpg b/test/fixtures/files/racecar-100x100-monochrome.jpg new file mode 100644 index 0000000000000000000000000000000000000000..39e683747e30e02ce454e8463280c1a4081ffe27 GIT binary patch literal 27586 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