activestorage-resumable 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,86 @@
1
+ import { ResumableUploadsController } from "./resumable_uploads_controller"
2
+ import { findElement } from "./helpers"
3
+
4
+ const processingAttribute = "data-resumable-uploads-processing"
5
+ const submitButtonsByForm = new WeakMap
6
+ let started = false
7
+
8
+ export function start() {
9
+ if (!started) {
10
+ started = true
11
+ document.addEventListener("click", didClick, true)
12
+ document.addEventListener("submit", didSubmitForm)
13
+ document.addEventListener("ajax:before", didSubmitRemoteElement)
14
+ }
15
+ }
16
+
17
+ function didClick(event) {
18
+ const { target } = event
19
+ if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) {
20
+ submitButtonsByForm.set(target.form, target)
21
+ }
22
+ }
23
+
24
+ function didSubmitForm(event) {
25
+ handleFormSubmissionEvent(event)
26
+ }
27
+
28
+ function didSubmitRemoteElement(event) {
29
+ if (event.target.tagName == "FORM") {
30
+ handleFormSubmissionEvent(event)
31
+ }
32
+ }
33
+
34
+ function handleFormSubmissionEvent(event) {
35
+ const form = event.target
36
+
37
+ if (form.hasAttribute(processingAttribute)) {
38
+ event.preventDefault()
39
+ return
40
+ }
41
+
42
+ const controller = new ResumableUploadsController(form)
43
+ const { inputs } = controller
44
+
45
+ if (inputs.length) {
46
+ event.preventDefault()
47
+ form.setAttribute(processingAttribute, "")
48
+ inputs.forEach(disable)
49
+ controller.start(error => {
50
+ form.removeAttribute(processingAttribute)
51
+ if (error) {
52
+ inputs.forEach(enable)
53
+ } else {
54
+ submitForm(form)
55
+ }
56
+ })
57
+ }
58
+ }
59
+
60
+ function submitForm(form) {
61
+ let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]")
62
+
63
+ if (button) {
64
+ const { disabled } = button
65
+ button.disabled = false
66
+ button.focus()
67
+ button.click()
68
+ button.disabled = disabled
69
+ } else {
70
+ button = document.createElement("input")
71
+ button.type = "submit"
72
+ button.style.display = "none"
73
+ form.appendChild(button)
74
+ button.click()
75
+ form.removeChild(button)
76
+ }
77
+ submitButtonsByForm.delete(form)
78
+ }
79
+
80
+ function disable(input) {
81
+ input.disabled = true
82
+ }
83
+
84
+ function enable(input) {
85
+ input.disabled = false
86
+ }
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ scope ActiveStorage.routes_prefix do
5
+ post '/resumable_uploads' => 'active_storage/resumable_uploads#create', as: :rails_resumable_uploads
6
+ put '/resumable_uploads/:signed_blob_id' => 'active_storage/resumable_uploads#update', as: :update_rails_resumable_upload
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class AddResumableUrlToActiveStorageBlobs < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :active_storage_blobs, :resumable_url, :text
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveSupport.on_load(:action_view) do
4
+ module ActionView
5
+ module Helpers
6
+ module FormTagHelper
7
+ private
8
+
9
+ def convert_direct_upload_option_to_url(options)
10
+ if options.delete(:direct_upload) && main_app.respond_to?(:rails_direct_uploads_url)
11
+ options['data-direct-upload-url'] = main_app.rails_direct_uploads_url
12
+ elsif options.delete(:resumable_upload) && main_app.respond_to?(:rails_resumable_uploads_url)
13
+ options['data-resumable-upload-url'] = main_app.rails_resumable_uploads_url
14
+ end
15
+ options
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'activestorage/resumable/engine'
4
+ require 'activestorage_extensions/service/gcs_service'
5
+ require 'activestorage_extensions/blob'
6
+ require 'actionview_extensions/helpers/form_tag_helper'
7
+
8
+ module ActiveStorage
9
+ module Resumable
10
+ mattr_accessor :upload_origin_url
11
+ @@upload_origin_url = ENV['UPLOAD_ORIGIN_URL']
12
+
13
+ def self.configure
14
+ yield self
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ module Resumable
5
+ class Engine < ::Rails::Engine
6
+ engine_name 'active_storage_resumable'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ module Resumable
5
+ VERSION = '1.0.0'
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveSupport.on_load(:active_storage_blob) do
4
+ class ActiveStorage::Blob
5
+ before_create do
6
+ self.resumable_url = service.url_for_resumable_upload(key, content_type)
7
+ end
8
+
9
+ scope :resumable, -> { unattached.where.not(resumable_url: nil) }
10
+ scope :active_resumable, -> { resumable.where('active_storage_blobs.created_at >= ?', 7.days.ago) }
11
+ scope :expired_resumable, -> { resumable.where('active_storage_blobs.created_at < ?', 7.days.ago) }
12
+
13
+ def self.find_or_create_before_resumable_upload!(filename:, byte_size:, checksum:, content_type: nil, metadata: nil)
14
+ ActiveStorage::Blob.expired_resumable.destroy_all
15
+ find_or_create_by! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type,
16
+ metadata: metadata
17
+ end
18
+
19
+ def uploaded_bytes
20
+ return unless resumable_url
21
+
22
+ @uploaded_bytes ||= begin
23
+ uri = URI(resumable_url)
24
+ req = Net::HTTP::Put.new(resumable_url, 'Content-Range': "bytes */#{byte_size}")
25
+ http = Net::HTTP.new(uri.hostname, uri.port)
26
+ http.use_ssl = true
27
+ res = http.request(req)
28
+ res['Range'].split('-').last.to_i + 1
29
+ end
30
+ end
31
+
32
+ def service_url_for_resumable_upload(expires_in: ActiveStorage.service_urls_expire_in)
33
+ service.url_for_resumable_upload key, expires_in: expires_in, content_type: content_type,
34
+ content_length: byte_size, checksum: checksum
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveSupport.on_load(:after_initialize) do
4
+ require 'active_storage/service/gcs_service'
5
+
6
+ module ActiveStorage
7
+ class Service::GCSService
8
+ def url_for_resumable_upload(key, content_type, **)
9
+ instrument :url, key: key do |payload|
10
+ signed_url = bucket.signed_url(key, method: 'PUT', content_type: content_type, version: :v4)
11
+
12
+ uri = URI.parse(signed_url)
13
+ https = Net::HTTP.new(uri.host, uri.port)
14
+ https.use_ssl = true
15
+
16
+ headers = {
17
+ 'Origin': ActiveStorage::Resumable.upload_origin_url,
18
+ 'x-goog-resumable': 'start'
19
+ }
20
+ request = Net::HTTP::Put.new(uri.request_uri, headers)
21
+ response = https.request(request)
22
+
23
+ generated_url = response['location']
24
+
25
+ payload[:url] = generated_url
26
+
27
+ generated_url
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activestorage-resumable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Kadu Diógenes
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-10-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activestorage
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 5.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 5.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 5.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Adds resumable support for ActiveStorage.
56
+ email:
57
+ - kadu@fnix.com.br
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - app/assets/javascripts/activestorage-resumable.js
66
+ - app/controllers/active_storage/resumable_uploads_controller.rb
67
+ - app/javascript/activestorage-resumable/helpers.js
68
+ - app/javascript/activestorage-resumable/index.js
69
+ - app/javascript/activestorage-resumable/resumable_blob_record.js
70
+ - app/javascript/activestorage-resumable/resumable_blob_upload.js
71
+ - app/javascript/activestorage-resumable/resumable_file_checksum.js
72
+ - app/javascript/activestorage-resumable/resumable_upload.js
73
+ - app/javascript/activestorage-resumable/resumable_upload_controller.js
74
+ - app/javascript/activestorage-resumable/resumable_uploads_controller.js
75
+ - app/javascript/activestorage-resumable/ujs-resumable.js
76
+ - config/routes.rb
77
+ - db/migrate/20190825001409_add_resumable_url_to_active_storage_blobs.rb
78
+ - lib/actionview_extensions/helpers/form_tag_helper.rb
79
+ - lib/activestorage/resumable.rb
80
+ - lib/activestorage/resumable/engine.rb
81
+ - lib/activestorage/resumable/version.rb
82
+ - lib/activestorage_extensions/blob.rb
83
+ - lib/activestorage_extensions/service/gcs_service.rb
84
+ homepage: https://github.com/fnix/activestorage-resumable
85
+ licenses:
86
+ - MIT
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubygems_version: 3.0.3
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: Adds resumable support for ActiveStorage.
107
+ test_files: []