├── .gitignore ├── .ruby-version ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── mountable_file_server.rb └── mountable_file_server │ ├── adapter.rb │ ├── file_accessor.rb │ ├── metadata.rb │ ├── rails.rb │ ├── server.rb │ ├── storage.rb │ ├── unique_identifier.rb │ ├── uri.rb │ └── version.rb ├── mountable_file_server.gemspec ├── shell.nix ├── test ├── acceptance │ └── form_interactions_test.rb ├── acceptance_helper.rb ├── fixtures │ ├── david.jpg │ ├── image.png │ └── test.txt ├── integration │ └── server_test.rb ├── integration_helper.rb ├── rails-dummy │ ├── README.rdoc │ ├── Rakefile │ ├── app │ │ ├── assets │ │ │ ├── config │ │ │ │ └── manifest.js │ │ │ ├── images │ │ │ │ └── .keep │ │ │ ├── javascripts │ │ │ │ ├── application.js │ │ │ │ └── users.js │ │ │ └── stylesheets │ │ │ │ ├── application.css │ │ │ │ ├── scaffold.css │ │ │ │ └── users.css │ │ ├── controllers │ │ │ ├── application_controller.rb │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ └── users_controller.rb │ │ ├── helpers │ │ │ ├── application_helper.rb │ │ │ └── users_helper.rb │ │ ├── mailers │ │ │ └── .keep │ │ ├── models │ │ │ ├── .keep │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ └── user.rb │ │ └── views │ │ │ ├── layouts │ │ │ └── application.html.erb │ │ │ └── users │ │ │ ├── _form.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── index.html.erb │ │ │ ├── new.html.erb │ │ │ └── show.html.erb │ ├── bin │ │ ├── bundle │ │ ├── rails │ │ └── rake │ ├── config.ru │ ├── config │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── database.yml │ │ ├── environment.rb │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── initializers │ │ │ ├── assets.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── cookies_serializer.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── inflections.rb │ │ │ ├── mime_types.rb │ │ │ ├── mountable_file_server.rb │ │ │ ├── session_store.rb │ │ │ └── wrap_parameters.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── routes.rb │ │ └── secrets.yml │ ├── db │ │ ├── migrate │ │ │ └── 20150305204311_create_users.rb │ │ └── schema.rb │ ├── lib │ │ └── assets │ │ │ └── .keep │ ├── log │ │ └── .keep │ └── public │ │ ├── 404.html │ │ ├── 422.html │ │ ├── 500.html │ │ └── favicon.ico ├── support │ └── path_helper.rb ├── unit │ ├── adapter_test.rb │ ├── file_accessor_test.rb │ ├── metadata_test.rb │ ├── storage_test.rb │ ├── unique_identifier_test.rb │ └── uri_test.rb └── unit_helper.rb └── vendor └── assets └── javascripts └── mountable_file_server.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | *.log 16 | *.sqlite3 17 | test/rails-dummy/tmp/ 18 | test/rails-dummy/uploads/ 19 | /.local-data/ 20 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.5.8 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.3 4 | - 2.1.7 5 | - 2.0.0 6 | - 1.9.3 7 | before_script: 8 | - cd test/rails-dummy 9 | - bundle exec rake db:create db:migrate 10 | - cd ../../ 11 | script: 12 | - bundle exec rake test 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.0.3 2 | * Relax required version number of `dry-configurable` dependency 3 | 4 | # 3.0.2 5 | * Fix deprecation warning when calling metadata class initializer with keyword arguments. 6 | * Don't break when uploading a private file. 7 | 8 | # 3.0.1 9 | * Repair broken `FileAccessor` class. 10 | 11 | # 3.0.0 12 | * **Breaking Change**: Remove `MountableFileServer::Client`. Use `MountableFileServer::Adapter` instead. 13 | 14 | # 2.1.0 15 | * Relax Sinatra dependency so Ruby on Rails 4 can use Rack 1.X 16 | 17 | # 2.0.0 18 | * Remove HTTP endpoints for moving and deleting uploads due to security concerns. 19 | * Return 404 for unknown or malformed FIDs. 20 | 21 | # 0.0.2 - 24.08.2015 22 | * Internal refactorings. 23 | * Introduce `Adapter` class to have one point of contact. 24 | 25 | **Breaking Changes** 26 | * Files are stored under their full unique identifier in every storage. 27 | * Remove `Access` class. 28 | * Signature of `Storage` methods changed. 29 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in mountable_file_server.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 David Strauß 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Mountable File Server can be used with any Ruby (on Rails) application and it removes the pain associated with file uploads. 2 | 3 | ## Core Idea 4 | app only stores reference to uploaded file 5 | 6 | ## MountableFileServer::Server 7 | The core of Mountable File Server (MFS) is a small HTTP API that accepts file uploads and offers endpoints to interact with uploaded files. While the frontend deals directly with the HTTP API, your Ruby application will want to use the Ruby adapter `MountableFileServer::Adapter`. 8 | 9 | 10 | 11 | 12 | 13 | 14 | MountableFileServer was born out of my frustations with existing Ruby based file upload solutions. It is certainly not comparable with existing feature-rich plug-and-play solutions that are tied to Ruby on Rails. 15 | 16 | The fundamental idea is that your application should not be tied to files or handling the upload process. Instead this is handled by a dedicated server which can be run as independet process or be mounted inside your application. It accepts file uploads and returns an unique identifier for the file. This unique identifier is basically a string and the only thing your application has to remember in order to work with uploaded files. 17 | 18 | * JavaScript function to upload files via AJAX. 19 | * JavaScript events regarding the uploads various states. 20 | * Uploaded files are stored on disk. 21 | * Filenames are unique and random sequences of charaters. 22 | * Uploaded files are stored temporary until they are explicitly moved to permanent storage by the application. 23 | * Uploaded files can be of public or private nature. 24 | * Private files can't be accessed via an URL. 25 | 26 | Things it does not do: 27 | 28 | * Image processing. Processing images has nothing to do with uploading files. [MountableImageProcessor]() is recommended for adding on the fly image processing to your application. 29 | 30 | ## Install & Setup 31 | Add the MountableFileServer gem to your Gemfile and `bundle install`. Make sure you include `vendor/assets/javascripts/mountable_file_server.js` in your frontend code. 32 | 33 | ~~~ruby 34 | gem 'mountable_file_server', '~> 0.0.2' 35 | ~~~ 36 | 37 | If your application is built upon Ruby on Rails you can add a require statement in your Gemfile. This includes an Ruby on Rails engine which allows you to require the JavaScript. 38 | 39 | ~~~ruby 40 | gem 'mountable_file_server', '~> 0.0.2', require: 'mountable_file_server/rails' 41 | ~~~ 42 | 43 | ~~~javascript 44 | //= require mountable_file_server 45 | ~~~ 46 | 47 | Once installed it is time to run the server so file uploads can be made. The server is called `MountableFileServer::Endpoint` and is a minimal Sinatra application which can be run as usual. You can also mount an endpoint inside your existing application. With Ruby on Rails this would look like this. 48 | 49 | ~~~ruby 50 | Rails.application.routes.draw do 51 | mount MountableFileServer::Endpoint, at: MountableFileServer.configuration.mounted_at 52 | end 53 | ~~~ 54 | 55 | ## Configuration 56 | As seen in the previous section there is a global configuration at `MountableFileServer.configuration` available. 57 | This is an instance of the `MountableFileServer::Configration` class. The global configuration is a default argument for all classes that require access to the configuration. In situations where you have multiple endpoints with different settings you can pass in you own configuration objects instead. 58 | 59 | The global configuration can be configured through a block. 60 | 61 | ~~~ruby 62 | MountableFileServer.configure do |configuration| 63 | configuration.mounted_at = '/uploads' 64 | configuration.stored_at = '/path/to/desired/uploads/directory' 65 | end 66 | ~~~ 67 | 68 | In order to build correct URLs for public files the `mounted_at` attribute should be set to the path where the endpoint is mounted. 69 | 70 | The `stored_at` attribute tells the MountableFileServer where to store uploaded files. It will automatically create this and all needed subdirectories. This directory should not be within the root of your webserver. Otherwise private files are accessible to the internet. 71 | 72 | ## Usage 73 | The idea is to upload a file with AJAX rather than sending it with the usual form submit. After the upload, an unique identifier (`uid`), is added to the form as a hidden element. Instead of dealing with a file upload your application only has to store the `uid`, a simple string value. 74 | 75 | Using the `MountableFileServer::Adapter` your application can work with the uploaded file, the only thing needed is the `uid`. 76 | 77 | ## JavaScript API 78 | On the JavaScript side you use `uploadFiles(element, files)` to upload files to the endpoint. The `element` argument is a DOM element that needs two data attributes. `data-endpoint` specifies where the AJAX request should be sent to, this probably matches the `mounted_at` configuration option. `data-type` tells the endpoint if the uploaded files should be treated as public or private files. Possible values are `public` or `private`. 79 | 80 | The `files` argument is an array of `File` objects or an `FileList` object. These objects are for example returned when a user selects files using an `input` element. 81 | 82 | Following events will be dispatched on the element that was passed to `uploadFiles`. When you are listening to one of these events you can access the described attributes on the `event.detail` object. 83 | 84 | `upload:start` is dispatched when the upload starts. 85 | It has the attributes `uploadId` and `file`. The `uploadId` is local and can be used to identify events in a scenario where multiple files are uploaded. The `file` attribute is the original `File` object and useful for showing a preview or other information about the file. 86 | 87 | `upload:progress` is continuously dispatched while the upload is happening. 88 | It has the attributes `uploadId` and `progress`. The `progress` attribute is the original [ProgressEvent](https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent) object of the AJAX request. 89 | 90 | `upload:success` is dispatched when the upload succeeded. 91 | It has the attributes `uploadId`, `uid` and `wasLastUpload`. The `uid` attribute is the unique identifier generated by the MountableFileServer. You will want to add it to your form and store it along your other data. The `wasLastUpload` attribute indicates if this was the last upload in progress. 92 | 93 | ## Ruby API 94 | The `MountableFileServer::Adapter` class allows you to interact with uploaded files. It takes a `MountableFileServer::Configuration` instance as argument and uses `MountableFileServer.configuration` by default. 95 | 96 | `MountableFileServer::Adapter#store_temporary(input, type, extension)` 97 | Stores the input as file in the temporary storage and returns the `uid` of the file. `input` can be a path to a file or an [IO](http://ruby-doc.org/core-2.2.2/IO.html) object. `type` can be `public` or `private` and the `extension` argument specifies the extension the file should have. 98 | 99 | `MountableFileServer::Adapter#store_permanent(input, type, extension)` 100 | Stores the input as file in the permanent storage and returns the `uid` of the file. `input` can be a path to a file or an [IO](http://ruby-doc.org/core-2.2.2/IO.html) object. `type` can be `public` or `private` and the `extension` argument specifies the extension the file should have. 101 | 102 | `MountableFileServer::Adapter#move_to_permanent_storage(uid)` 103 | Moves a file from the temporary storage to the permanent one. This is mostly used in a scenario where users upload files. A file uploaded through the endpoint is initially only stored in the temporary storage. The application has to move it explicitly to the permanent storage. For example after all validations passed. 104 | 105 | `MountableFileServer::Adapter#remove_from_permanent_storage(uid)` 106 | Removes the file from the permanent storage. 107 | 108 | `MountableFileServer::Adapter#url_for(uid)` 109 | Returns the URL for an uploaded file. Only works for public files, if you pass the `uid` of a private file an error will be raised. 110 | 111 | `MountableFileServer::Adapter#pathname_for(id)` 112 | Returns a [Pathname](http://ruby-doc.org/stdlib-2.2.2/libdoc/pathname/rdoc/Pathname.html) object for the uploaded file. The pathname will always point to the file on disk independent from the files type or current storage location. 113 | 114 | # Development 115 | Run the migrations of the Ruby on Rails dummy application to make sure you can run the tests: `cd test/rails-dummy && RAILS_ENV=test bundle exec rake db:migrate`. 116 | 117 | Run tests with `bundle exec rake test`. 118 | 119 | # Publish on RubyGems.org 120 | 121 | 1. Increment `lib/mountable_image_server/version.rb` to your liking. 122 | 2. Make a Git commit. 123 | 3. Run `bundle exec rake release`. 124 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test/" 6 | t.libs << "test/acceptance" 7 | t.pattern = "test/**/*_test.rb" 8 | end 9 | 10 | task :default => :test 11 | 12 | -------------------------------------------------------------------------------- /lib/mountable_file_server.rb: -------------------------------------------------------------------------------- 1 | require "dry-configurable" 2 | 3 | require "mountable_file_server/version" 4 | 5 | module MountableFileServer 6 | extend Dry::Configurable 7 | 8 | setting :base_url 9 | setting :storage_path 10 | end 11 | -------------------------------------------------------------------------------- /lib/mountable_file_server/adapter.rb: -------------------------------------------------------------------------------- 1 | require 'mountable_file_server/storage' 2 | require 'mountable_file_server/unique_identifier' 3 | require 'mountable_file_server/file_accessor' 4 | 5 | module MountableFileServer 6 | class Adapter 7 | attr_reader :configuration 8 | 9 | def initialize(configuration = MountableFileServer.config) 10 | @configuration = configuration 11 | end 12 | 13 | def store_temporary(input, type, extension) 14 | uid = generate_random_uid type, extension 15 | Storage.new(configuration).store_temporary uid, input 16 | uid 17 | end 18 | 19 | def store_permanent(input, type, extension) 20 | uid = generate_random_uid type, extension 21 | Storage.new(configuration).store_permanent uid, input 22 | uid 23 | end 24 | 25 | def move_to_permanent_storage(uid) 26 | uid = UniqueIdentifier.new uid 27 | Storage.new(configuration).move_to_permanent_storage uid 28 | end 29 | 30 | def remove_from_storage(uid) 31 | uid = UniqueIdentifier.new uid 32 | Storage.new(configuration).remove_from_storage uid 33 | end 34 | 35 | def url_for(uid) 36 | uid = UniqueIdentifier.new uid 37 | FileAccessor.new(uid, configuration).url 38 | end 39 | 40 | def pathname_for(uid) 41 | uid = UniqueIdentifier.new uid 42 | FileAccessor.new(uid, configuration).pathname 43 | end 44 | 45 | private 46 | def generate_random_uid(type, extension) 47 | loop do 48 | uid = UniqueIdentifier.generate_for type, extension 49 | break uid unless FileAccessor.new(uid, configuration).exist? 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/mountable_file_server/file_accessor.rb: -------------------------------------------------------------------------------- 1 | require 'mountable_file_server/uri' 2 | require 'pathname' 3 | 4 | module MountableFileServer 5 | MissingFile = Class.new(ArgumentError) 6 | NotAccessibleViaURL = Class.new(ArgumentError) 7 | 8 | class FileAccessor 9 | attr_reader :uid, :configuration 10 | 11 | def initialize(uid, configuration = MountableFileServer.config) 12 | @uid = uid 13 | @configuration = configuration 14 | end 15 | 16 | def temporary_pathname 17 | Pathname(configuration.storage_path) + 'tmp' + uid 18 | end 19 | 20 | def permanent_pathname 21 | Pathname(configuration.storage_path) + uid.type + uid 22 | end 23 | 24 | def pathname 25 | pathnames.find(-> { raise MissingFile }) { |p| p.file? } 26 | end 27 | 28 | def exist? 29 | pathnames.any? { |p| p.file? } 30 | end 31 | 32 | def url 33 | raise NotAccessibleViaURL unless uid.public? 34 | 35 | URI.new (Pathname(configuration.base_url) + uid).to_s 36 | end 37 | 38 | private 39 | def pathnames 40 | [permanent_pathname, temporary_pathname] 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/mountable_file_server/metadata.rb: -------------------------------------------------------------------------------- 1 | require 'dimensions' 2 | 3 | module MountableFileServer 4 | class Metadata 5 | attr_reader :size, :content_type, :height, :width 6 | 7 | def initialize(size:, content_type:, width: nil, height: nil) 8 | @size = size 9 | @content_type = content_type 10 | @width = width 11 | @height = height 12 | end 13 | 14 | def to_h 15 | hash = { 16 | size: size, 17 | content_type: content_type 18 | } 19 | 20 | if width && height 21 | hash = hash.merge({ 22 | height: height, 23 | width: width 24 | }) 25 | end 26 | 27 | hash 28 | end 29 | 30 | def self.for_path(path) 31 | parameters = {} 32 | 33 | parameters[:content_type] = `file --brief --mime-type #{path}`.strip 34 | parameters[:size] = File.size(path) 35 | 36 | if ['image/png', 'image/jpeg', 'image/gif', 'image/tiff'].include?(parameters[:content_type]) 37 | dimensions = Dimensions.dimensions(path) 38 | parameters[:width] = dimensions[0] 39 | parameters[:height] = dimensions[1] 40 | end 41 | 42 | new(**parameters) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/mountable_file_server/rails.rb: -------------------------------------------------------------------------------- 1 | require 'mountable_file_server' 2 | require 'mountable_file_server/server' 3 | 4 | module MountableFileServer 5 | class Engine < Rails::Engine 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/mountable_file_server/server.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'pathname' 3 | 4 | require 'mountable_file_server/adapter' 5 | require 'mountable_file_server/metadata' 6 | 7 | module MountableFileServer 8 | class Server < Sinatra::Base 9 | post '/' do 10 | adapter = Adapter.new 11 | pathname = Pathname(params[:file][:tempfile].path) 12 | type = params[:type] 13 | fid = adapter.store_temporary(pathname, type, pathname.extname) 14 | url = adapter.url_for(fid) if fid.public? 15 | metadata = Metadata.for_path(pathname) 16 | 17 | content_type :json 18 | status 201 19 | 20 | { 21 | fid: fid, 22 | url: url, 23 | metadata: metadata.to_h 24 | }.to_json 25 | end 26 | 27 | get '/:fid' do |fid| 28 | begin 29 | adapter = Adapter.new 30 | pathname = adapter.pathname_for(fid) 31 | send_file pathname 32 | rescue MissingFile, MalformedIdentifier 33 | status 404 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/mountable_file_server/storage.rb: -------------------------------------------------------------------------------- 1 | module MountableFileServer 2 | class Storage 3 | attr_reader :configuration 4 | 5 | def initialize(configuration = MountableFileServer.config) 6 | @configuration = configuration 7 | end 8 | 9 | def store_temporary(uid, input) 10 | destination = FileAccessor.new(uid, configuration).temporary_pathname 11 | destination.dirname.mkpath 12 | IO.copy_stream input, destination 13 | end 14 | 15 | def store_permanent(uid, input) 16 | destination = FileAccessor.new(uid, configuration).permanent_pathname 17 | destination.dirname.mkpath 18 | IO.copy_stream input, destination 19 | end 20 | 21 | def move_to_permanent_storage(uid) 22 | source = FileAccessor.new(uid, configuration).temporary_pathname 23 | destination = FileAccessor.new(uid, configuration).permanent_pathname 24 | destination.dirname.mkpath 25 | 26 | source.rename destination 27 | end 28 | 29 | def remove_from_storage(uid) 30 | source = FileAccessor.new(uid, configuration).pathname 31 | source.delete 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/mountable_file_server/unique_identifier.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | module MountableFileServer 4 | UnknownType = Class.new(ArgumentError) 5 | MalformedIdentifier = Class.new(ArgumentError) 6 | 7 | class UniqueIdentifier < String 8 | attr_reader :type, :filename 9 | 10 | def initialize(string) 11 | raise MalformedIdentifier.new unless /(\w+)-(.+)$/.match(string) 12 | 13 | @type, @filename = /(\w+)-(.+)$/.match(string).captures 14 | 15 | raise UnknownType.new(type) unless known_type? 16 | 17 | super.freeze 18 | end 19 | 20 | def self.generate_for(type, extension) 21 | new "#{type}-#{SecureRandom.hex}#{extension}" 22 | end 23 | 24 | def public? 25 | type == 'public' 26 | end 27 | 28 | private 29 | def known_type? 30 | ['public', 'private'].include?(type) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/mountable_file_server/uri.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | require 'pathname' 3 | 4 | module MountableFileServer 5 | class URI < String 6 | def initialize(string) 7 | super.freeze 8 | end 9 | 10 | def uid 11 | UniqueIdentifier.new filename 12 | end 13 | 14 | def filename 15 | Pathname(::URI.parse(self).path).basename.to_s 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/mountable_file_server/version.rb: -------------------------------------------------------------------------------- 1 | module MountableFileServer 2 | VERSION = "3.0.3" 3 | end 4 | -------------------------------------------------------------------------------- /mountable_file_server.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'mountable_file_server/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "mountable_file_server" 8 | spec.version = MountableFileServer::VERSION 9 | spec.authors = ["David Strauß"] 10 | spec.email = ["david@strauss.io"] 11 | spec.summary = %q{Simple mountable server that handles file uploads} 12 | spec.description = %q{} 13 | spec.homepage = "https://github.com/stravid/mountable_file_server" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 2.1" 22 | spec.add_development_dependency "rake", ">= 12.3.3" 23 | spec.add_development_dependency "minitest", "~> 5.8.0" 24 | spec.add_development_dependency "rails", "~> 5.0.7.2" 25 | spec.add_development_dependency "capybara", "2.18.0" 26 | spec.add_development_dependency "sqlite3", "~> 1.3.10" 27 | spec.add_development_dependency "poltergeist", "1.18.1" 28 | spec.add_development_dependency "rack-test", "~> 0.6.3" 29 | spec.add_development_dependency "mocha", "1.11.2" 30 | 31 | spec.add_runtime_dependency "sinatra", ">= 1.4.8" 32 | spec.add_runtime_dependency "dry-configurable", ">= 0.1.6" 33 | spec.add_runtime_dependency "dimensions", "~> 1.3.0" 34 | end 35 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import (fetchGit { 3 | url = https://github.com/NixOS/nixpkgs-channels; 4 | ref = "nixos-20.03"; 5 | }) {}, 6 | ruby ? pkgs.ruby_2_5, 7 | bundler ? pkgs.bundler.override { inherit ruby; } 8 | }: 9 | 10 | pkgs.mkShell { 11 | buildInputs = with pkgs; [ 12 | ruby 13 | bundler 14 | git 15 | sqlite 16 | zlib 17 | phantomjs 18 | rubyPackages_2_5.nokogiri 19 | libiconv 20 | ]; 21 | 22 | shellHook = '' 23 | mkdir -p .local-data/gems 24 | export GEM_HOME=$PWD/.local-data/gems 25 | export GEM_PATH=$GEM_HOME 26 | ''; 27 | } 28 | -------------------------------------------------------------------------------- /test/acceptance/form_interactions_test.rb: -------------------------------------------------------------------------------- 1 | require 'acceptance_helper' 2 | 3 | class TestFormInteractions < AcceptanceTestCase 4 | def setup 5 | MountableFileServer.configure do |config| 6 | config.base_url = "/uploads" 7 | end 8 | 9 | User.destroy_all 10 | end 11 | 12 | def test_upload_client_side_interaction 13 | visit "/users/new" 14 | attach_file("Avatar url", fixture_path('david.jpg')) 15 | 16 | sleep 0.1 17 | 18 | assert_match(/public-.*.jpg/, find('.js-mountable-file-server-input input[type=hidden]', visible: false).value) 19 | assert has_content?("Upload started.") 20 | assert has_content?("Upload succeeded.") 21 | end 22 | 23 | def test_upload 24 | visit "/users/new" 25 | fill_in "Name", with: "David" 26 | attach_file "Avatar url", fixture_path('david.jpg') 27 | 28 | sleep 0.1 29 | 30 | click_button "Create User" 31 | 32 | visit find("img")[:src] 33 | 34 | assert_equal 200, page.status_code 35 | assert_match(/^image\//, page.response_headers['Content-Type']) 36 | end 37 | 38 | def test_remove_upload 39 | visit "/users/new" 40 | fill_in "Name", with: "David" 41 | attach_file "Avatar url", fixture_path('david.jpg') 42 | 43 | sleep 0.1 44 | 45 | click_button "Create User" 46 | 47 | visit "/users" 48 | click_link "Destroy" 49 | 50 | assert_equal 200, page.status_code 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/acceptance_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | 3 | require 'minitest/autorun' 4 | require 'minitest/hell' 5 | 6 | ENV['RAILS_ENV'] ||= 'test' 7 | require File.expand_path("../rails-dummy/config/environment.rb", __FILE__) 8 | require 'capybara/rails' 9 | require 'capybara/poltergeist' 10 | require 'support/path_helper' 11 | 12 | Capybara.current_driver = :poltergeist 13 | 14 | class AcceptanceTestCase < MiniTest::Test 15 | include Capybara::DSL 16 | include PathHelper 17 | 18 | def setup 19 | end 20 | 21 | def teardown 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/fixtures/david.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgycircle/mountable_file_server/b253975eee4135df5b67dd3ee328341a5fd15c5a/test/fixtures/david.jpg -------------------------------------------------------------------------------- /test/fixtures/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgycircle/mountable_file_server/b253975eee4135df5b67dd3ee328341a5fd15c5a/test/fixtures/image.png -------------------------------------------------------------------------------- /test/fixtures/test.txt: -------------------------------------------------------------------------------- 1 | Test 2 | -------------------------------------------------------------------------------- /test/integration/server_test.rb: -------------------------------------------------------------------------------- 1 | require 'integration_helper' 2 | require 'json' 3 | 4 | class TestServer < IntegrationTestCase 5 | def setup 6 | MountableFileServer.configure do |config| 7 | config.base_url = 'http://test.test/uploads/' 8 | config.storage_path = Dir.mktmpdir 9 | end 10 | end 11 | 12 | def teardown 13 | Pathname(MountableFileServer.config.storage_path).rmtree 14 | end 15 | 16 | def test_file_upload 17 | post '/', { 18 | file: Rack::Test::UploadedFile.new(fixture_path('image.png'), 'image/jpeg'), 19 | type: 'public' 20 | } 21 | 22 | result = JSON.parse(last_response.body, symbolize_names: true) 23 | 24 | assert_equal 201, last_response.status 25 | assert_equal 'application/json', last_response.headers['Content-Type'] 26 | assert_match(/public-\w{32}\.png/, result[:fid]) 27 | assert_equal "http://test.test/uploads/#{result[:fid]}", result[:url] 28 | assert_equal File.size(fixture_path('image.png')), result[:metadata][:size] 29 | assert_equal 'image/png', result[:metadata][:content_type] 30 | assert_equal 269, result[:metadata][:width] 31 | assert_equal 234, result[:metadata][:height] 32 | end 33 | 34 | def test_private_file_upload 35 | post '/', { 36 | file: Rack::Test::UploadedFile.new(fixture_path('image.png'), 'image/jpeg'), 37 | type: 'private' 38 | } 39 | 40 | result = JSON.parse(last_response.body, symbolize_names: true) 41 | 42 | assert_equal 201, last_response.status 43 | assert_equal 'application/json', last_response.headers['Content-Type'] 44 | assert_match(/private-\w{32}\.png/, result[:fid]) 45 | assert_equal nil, result[:url] 46 | assert_equal File.size(fixture_path('image.png')), result[:metadata][:size] 47 | assert_equal 'image/png', result[:metadata][:content_type] 48 | assert_equal 269, result[:metadata][:width] 49 | assert_equal 234, result[:metadata][:height] 50 | end 51 | 52 | def test_temporary_file_download 53 | post '/', { 54 | file: Rack::Test::UploadedFile.new(fixture_path('image.png'), 'image/jpeg'), 55 | type: 'public' 56 | } 57 | 58 | fid = JSON.parse(last_response.body, symbolize_names: true)[:fid] 59 | get fid 60 | 61 | Dir.mktmpdir('downloads') do |dir| 62 | File.open(File.join(dir, 'test.png'), 'wb') { |file| file.write(last_response.body) } 63 | assert_equal Pathname(fixture_path('image.png')).read, Pathname(File.join(dir, 'test.png')).read 64 | end 65 | end 66 | 67 | def test_download_of_unknown_fid 68 | fid = 'public-unknown.png' 69 | get fid 70 | 71 | assert_equal 404, last_response.status 72 | end 73 | 74 | def test_download_of_malformed_fid 75 | fid = 'bla.png' 76 | get fid 77 | 78 | assert_equal 404, last_response.status 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/integration_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | 3 | require 'minitest/autorun' 4 | require 'minitest/hell' 5 | require 'rack/test' 6 | require 'support/path_helper' 7 | require 'mountable_file_server' 8 | require 'mountable_file_server/server' 9 | 10 | class IntegrationTestCase < MiniTest::Test 11 | include Rack::Test::Methods 12 | include PathHelper 13 | 14 | def app 15 | @app = MountableFileServer::Server.new 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/rails-dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /test/rails-dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /test/rails-dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link application.css 2 | //= link application.js 3 | -------------------------------------------------------------------------------- /test/rails-dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgycircle/mountable_file_server/b253975eee4135df5b67dd3ee328341a5fd15c5a/test/rails-dummy/app/assets/images/.keep -------------------------------------------------------------------------------- /test/rails-dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require mountable_file_server 14 | //= require_tree . 15 | 16 | document.addEventListener('DOMContentLoaded', function() { 17 | var form = document.querySelector("form"); 18 | var input = document.querySelector("input[type=file]"); 19 | 20 | if (!form || !input) { return; } 21 | 22 | var uploader = new MountableFileServerUploader({ 23 | url: '/uploads' 24 | }); 25 | 26 | input.addEventListener('change', function(event) { 27 | var self = this; 28 | 29 | for (var i = 0; i < this.files.length; i++) { 30 | uploader.uploadFile({ 31 | file: this.files[i], 32 | type: 'public', 33 | onStart: function() { 34 | console.log('onStart'); 35 | 36 | var p = document.createElement("p"); 37 | p.textContent = "Upload started."; 38 | form.appendChild(p); 39 | }, 40 | onProgress: function(progress) { 41 | console.log('onProgress', progress); 42 | 43 | var p = document.createElement("p"); 44 | p.textContent = "Upload progess " + progress.loaded + " of " + progress.total + "."; 45 | form.appendChild(p); 46 | }, 47 | onSuccess: function(response) { 48 | console.log('onSuccess', response); 49 | 50 | var $hiddenInput = document.createElement('input'); 51 | 52 | $hiddenInput.value = response.fid; 53 | $hiddenInput.name = self.name; 54 | $hiddenInput.type = 'hidden'; 55 | self.parentNode.insertBefore($hiddenInput, self.nextSibling); 56 | 57 | var p = document.createElement("p"); 58 | p.textContent = "Upload succeeded."; 59 | form.appendChild(p); 60 | } 61 | }); 62 | } 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/rails-dummy/app/assets/javascripts/users.js: -------------------------------------------------------------------------------- 1 | // Place all the behaviors and hooks related to the matching controller here. 2 | // All this logic will automatically be available in application.js. 3 | -------------------------------------------------------------------------------- /test/rails-dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /test/rails-dummy/app/assets/stylesheets/scaffold.css: -------------------------------------------------------------------------------- 1 | body { background-color: #fff; color: #333; } 2 | 3 | body, p, ol, ul, td { 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | pre { 10 | background-color: #eee; 11 | padding: 10px; 12 | font-size: 11px; 13 | } 14 | 15 | a { color: #000; } 16 | a:visited { color: #666; } 17 | a:hover { color: #fff; background-color:#000; } 18 | 19 | div.field, div.actions { 20 | margin-bottom: 10px; 21 | } 22 | 23 | #notice { 24 | color: green; 25 | } 26 | 27 | .field_with_errors { 28 | padding: 2px; 29 | background-color: red; 30 | display: table; 31 | } 32 | 33 | #error_explanation { 34 | width: 450px; 35 | border: 2px solid red; 36 | padding: 7px; 37 | padding-bottom: 0; 38 | margin-bottom: 20px; 39 | background-color: #f0f0f0; 40 | } 41 | 42 | #error_explanation h2 { 43 | text-align: left; 44 | font-weight: bold; 45 | padding: 5px 5px 5px 15px; 46 | font-size: 12px; 47 | margin: -7px; 48 | margin-bottom: 0px; 49 | background-color: #c00; 50 | color: #fff; 51 | } 52 | 53 | #error_explanation ul li { 54 | font-size: 12px; 55 | list-style: square; 56 | } 57 | -------------------------------------------------------------------------------- /test/rails-dummy/app/assets/stylesheets/users.css: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | */ 5 | -------------------------------------------------------------------------------- /test/rails-dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /test/rails-dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgycircle/mountable_file_server/b253975eee4135df5b67dd3ee328341a5fd15c5a/test/rails-dummy/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /test/rails-dummy/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :set_user, only: [:show, :edit, :update, :destroy] 3 | 4 | # GET /users 5 | def index 6 | @users = User.all 7 | end 8 | 9 | # GET /users/1 10 | def show 11 | @adapter = MountableFileServer::Adapter.new 12 | end 13 | 14 | # GET /users/new 15 | def new 16 | @user = User.new 17 | end 18 | 19 | # GET /users/1/edit 20 | def edit 21 | end 22 | 23 | # POST /users 24 | def create 25 | @user = User.new(user_params) 26 | 27 | if @user.save 28 | adapter = MountableFileServer::Adapter.new 29 | adapter.move_to_permanent_storage @user.avatar_url 30 | redirect_to @user, notice: 'User was successfully created.' 31 | else 32 | render :new 33 | end 34 | end 35 | 36 | # PATCH/PUT /users/1 37 | def update 38 | if @user.update(user_params) 39 | redirect_to @user, notice: 'User was successfully updated.' 40 | else 41 | render :edit 42 | end 43 | end 44 | 45 | # DELETE /users/1 46 | def destroy 47 | adapter = MountableFileServer::Adapter.new 48 | adapter.remove_from_storage @user.avatar_url 49 | @user.destroy 50 | redirect_to users_url, notice: 'User was successfully destroyed.' 51 | end 52 | 53 | private 54 | # Use callbacks to share common setup or constraints between actions. 55 | def set_user 56 | @user = User.find(params[:id]) 57 | end 58 | 59 | # Only allow a trusted parameter "white list" through. 60 | def user_params 61 | params.require(:user).permit(:name, :avatar_url) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/rails-dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/rails-dummy/app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/rails-dummy/app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgycircle/mountable_file_server/b253975eee4135df5b67dd3ee328341a5fd15c5a/test/rails-dummy/app/mailers/.keep -------------------------------------------------------------------------------- /test/rails-dummy/app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgycircle/mountable_file_server/b253975eee4135df5b67dd3ee328341a5fd15c5a/test/rails-dummy/app/models/.keep -------------------------------------------------------------------------------- /test/rails-dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgycircle/mountable_file_server/b253975eee4135df5b67dd3ee328341a5fd15c5a/test/rails-dummy/app/models/concerns/.keep -------------------------------------------------------------------------------- /test/rails-dummy/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/rails-dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |Name | 7 |Avatar url | 8 |9 | | ||
---|---|---|---|---|
<%= user.name %> | 16 |<%= user.avatar_url %> | 17 |<%= link_to 'Show', user %> | 18 |<%= link_to 'Edit', edit_user_path(user) %> | 19 |<%= link_to 'Destroy', destroy_user_path(user) %> | 20 |
<%= notice %>
2 | 3 |4 | Name: 5 | <%= @user.name %> 6 |
7 | 8 |9 | Avatar: 10 | <%= image_tag @adapter.url_for(@user.avatar_url) %> 11 |
12 | 13 | <%= link_to 'Edit', edit_user_path(@user) %> | 14 | <%= link_to 'Back', users_path %> 15 | -------------------------------------------------------------------------------- /test/rails-dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /test/rails-dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /test/rails-dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/rails-dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /test/rails-dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | Bundler.require(*Rails.groups) 6 | require "mountable_file_server/rails" 7 | 8 | module Dummy 9 | class Application < Rails::Application 10 | # Settings in config/environments/* take precedence over those specified here. 11 | # Application configuration should go into files in config/initializers 12 | # -- all .rb files in that directory are automatically loaded. 13 | 14 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 15 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 16 | # config.time_zone = 'Central Time (US & Canada)' 17 | 18 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 19 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 20 | # config.i18n.default_locale = :de 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /test/rails-dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /test/rails-dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /test/rails-dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/rails-dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Adds additional error checking when serving assets at runtime. 31 | # Checks for improperly declared sprockets dependencies. 32 | # Raises helpful error messages. 33 | config.assets.raise_runtime_errors = true 34 | 35 | # Raises error for missing translations 36 | # config.action_view.raise_on_missing_translations = true 37 | end 38 | -------------------------------------------------------------------------------- /test/rails-dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_files = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | # config.force_ssl = true 43 | 44 | # Set to :debug to see everything in the log. 45 | config.log_level = :info 46 | 47 | # Prepend all log lines with the following tags. 48 | # config.log_tags = [ :subdomain, :uuid ] 49 | 50 | # Use a different logger for distributed setups. 51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 52 | 53 | # Use a different cache store in production. 54 | # config.cache_store = :mem_cache_store 55 | 56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 57 | # config.action_controller.asset_host = "http://assets.example.com" 58 | 59 | # Ignore bad email addresses and do not raise email delivery errors. 60 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 61 | # config.action_mailer.raise_delivery_errors = false 62 | 63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 64 | # the I18n.default_locale when a translation cannot be found). 65 | config.i18n.fallbacks = true 66 | 67 | # Send deprecation notices to registered listeners. 68 | config.active_support.deprecation = :notify 69 | 70 | # Disable automatic flushing of the log to improve performance. 71 | # config.autoflush_log = false 72 | 73 | # Use default logging formatter so that PID and timestamp are not suppressed. 74 | config.log_formatter = ::Logger::Formatter.new 75 | 76 | # Do not dump schema after migrations. 77 | config.active_record.dump_schema_after_migration = false 78 | end 79 | -------------------------------------------------------------------------------- /test/rails-dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' } 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raises error for missing translations 38 | # config.action_view.raise_on_missing_translations = true 39 | end 40 | -------------------------------------------------------------------------------- /test/rails-dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Precompile additional assets. 7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 8 | # Rails.application.config.assets.precompile += %w( search.js ) 9 | -------------------------------------------------------------------------------- /test/rails-dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/rails-dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json -------------------------------------------------------------------------------- /test/rails-dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /test/rails-dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /test/rails-dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /test/rails-dummy/config/initializers/mountable_file_server.rb: -------------------------------------------------------------------------------- 1 | MountableFileServer.configure do |config| 2 | config.base_url = '/uploads/' 3 | 4 | if Rails.env.test? 5 | config.storage_path = File.join(Rails.root, 'tmp', 'test-uploads') 6 | else 7 | config.storage_path = File.join(Rails.root, 'uploads') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/rails-dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_dummy_session' 4 | -------------------------------------------------------------------------------- /test/rails-dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /test/rails-dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /test/rails-dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :users do 3 | get :destroy, on: :member, as: :destroy, path: 'destroy' 4 | end 5 | 6 | mount MountableFileServer::Server, at: '/uploads' 7 | end 8 | -------------------------------------------------------------------------------- /test/rails-dummy/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 63134486bdad8cb685c5e3a6da334a8ce3110934c63d7aebe5e0b2253036a74a33bfc06c068bce6fc79700ee2289deabe2cdee0e4a60c99f4de6e9bbc1906f8c 15 | 16 | test: 17 | secret_key_base: c1cc8edcb033db8be1ba51970c6b93886e8a2ec56d3735b3952e42b1a448b76bf9c65a97c5ccdda668bfcc25d0a437cacd6e92c460fe2c9f03145d20cf222e1e 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /test/rails-dummy/db/migrate/20150305204311_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :name 5 | t.string :avatar_url 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/rails-dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20150305204311) do 14 | 15 | create_table "users", force: :cascade do |t| 16 | t.string "name" 17 | t.string "avatar_url" 18 | t.datetime "created_at" 19 | t.datetime "updated_at" 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /test/rails-dummy/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgycircle/mountable_file_server/b253975eee4135df5b67dd3ee328341a5fd15c5a/test/rails-dummy/lib/assets/.keep -------------------------------------------------------------------------------- /test/rails-dummy/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgycircle/mountable_file_server/b253975eee4135df5b67dd3ee328341a5fd15c5a/test/rails-dummy/log/.keep -------------------------------------------------------------------------------- /test/rails-dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |You may have mistyped the address or the page may have moved.
63 |If you are the application owner check the logs for more information.
65 |Maybe you tried to change something you didn't have access to.
63 |If you are the application owner check the logs for more information.
65 |If you are the application owner check the logs for more information.
64 |