activestorage_saas 5.2.5.2 → 7.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +6 -3
- data/Gemfile.lock +190 -66
- data/Guardfile +42 -0
- data/Rakefile +5 -9
- data/activestorage_saas.gemspec +2 -5
- data/config/initializers/active_storage_saas.rb +6 -0
- data/lib/active_storage/service/saas_service.rb +15 -10
- data/lib/active_storage_saas/blob_model_mixin.rb +48 -0
- data/lib/active_storage_saas/direct_uploads_controller_mixin.rb +31 -0
- data/lib/active_storage_saas/engine.rb +26 -5
- data/lib/active_storage_saas/routes.rb +3 -3
- data/lib/active_storage_saas/storage_service_configuration_model_mixin.rb +47 -0
- data/lib/active_storage_saas.rb +4 -2
- data/lib/generators/active_storage_saas/install/USAGE +5 -0
- data/lib/generators/active_storage_saas/install/install_generator.rb +19 -0
- data/lib/generators/active_storage_saas/install/templates/config/initializers/active_storage_saas.rb +6 -0
- metadata +20 -16
- data/app/controller/active_storage_saas/direct_uploads_controller.rb +0 -28
- data/app/javascript/active_storage_saas/direct_upload_controller/blob_record.js +0 -73
- data/app/javascript/active_storage_saas/direct_upload_controller/blob_upload.js +0 -45
- data/app/javascript/active_storage_saas/direct_upload_controller/direct_upload.js +0 -48
- data/app/javascript/active_storage_saas/direct_upload_controller/file_checksum.js +0 -53
- data/app/javascript/active_storage_saas/direct_upload_controller/helpers.js +0 -51
- data/app/javascript/active_storage_saas/direct_upload_controller.js +0 -78
- data/app/models/tenant_storage_service.rb +0 -5
- data/db/migrate/001_activestorage_saas_tables.rb +0 -17
- data/lib/active_storage_saas/blob_patch.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2582d10000f7aae9780b0d273c172c4a76cb08c97bf092126979ce4110d00d8
|
4
|
+
data.tar.gz: 7f88a5f75a7a18bf1587d114e2b87d7d9888afd633d88488868910b495b2c72a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7d76f176c4a9c6ed75544f7008d874363c0cd9b80a372af94d93c8d24800835644fc18ed62e96546e4ea924491b789e8ec07e676e3aaa9bbc1e7b19bff4aad0
|
7
|
+
data.tar.gz: 5d9ed9018b5a423dc66ed88bec3b93e34abd12d060c11ca245767a9eb91aa22bf2115bbef8e73e1c5fdedf17a698c139303a641dd1da81ddbc617a30ce7fb998
|
data/Gemfile
CHANGED
@@ -5,8 +5,11 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in activestorage_saas.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem "
|
8
|
+
gem "rails", "~> 7.0"
|
9
|
+
gem "sqlite3"
|
10
|
+
gem "propshaft"
|
11
|
+
gem "rspec-rails", "~> 5.1"
|
9
12
|
|
10
|
-
gem "rspec", "~>
|
13
|
+
gem "guard-rspec", "~> 4.7"
|
11
14
|
|
12
|
-
gem "
|
15
|
+
gem "aws-sdk-s3", "~> 1.114"
|
data/Gemfile.lock
CHANGED
@@ -1,112 +1,236 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
activestorage_saas (
|
5
|
-
activestorage (
|
4
|
+
activestorage_saas (7.0.4)
|
5
|
+
activestorage (>= 7.0.3.1, <= 7.0.4)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
|
11
|
-
|
12
|
-
activesupport (=
|
13
|
-
|
10
|
+
actioncable (7.0.4)
|
11
|
+
actionpack (= 7.0.4)
|
12
|
+
activesupport (= 7.0.4)
|
13
|
+
nio4r (~> 2.0)
|
14
|
+
websocket-driver (>= 0.6.1)
|
15
|
+
actionmailbox (7.0.4)
|
16
|
+
actionpack (= 7.0.4)
|
17
|
+
activejob (= 7.0.4)
|
18
|
+
activerecord (= 7.0.4)
|
19
|
+
activestorage (= 7.0.4)
|
20
|
+
activesupport (= 7.0.4)
|
21
|
+
mail (>= 2.7.1)
|
22
|
+
net-imap
|
23
|
+
net-pop
|
24
|
+
net-smtp
|
25
|
+
actionmailer (7.0.4)
|
26
|
+
actionpack (= 7.0.4)
|
27
|
+
actionview (= 7.0.4)
|
28
|
+
activejob (= 7.0.4)
|
29
|
+
activesupport (= 7.0.4)
|
30
|
+
mail (~> 2.5, >= 2.5.4)
|
31
|
+
net-imap
|
32
|
+
net-pop
|
33
|
+
net-smtp
|
34
|
+
rails-dom-testing (~> 2.0)
|
35
|
+
actionpack (7.0.4)
|
36
|
+
actionview (= 7.0.4)
|
37
|
+
activesupport (= 7.0.4)
|
38
|
+
rack (~> 2.0, >= 2.2.0)
|
14
39
|
rack-test (>= 0.6.3)
|
15
40
|
rails-dom-testing (~> 2.0)
|
16
|
-
rails-html-sanitizer (~> 1.0, >= 1.0
|
17
|
-
|
18
|
-
|
41
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
42
|
+
actiontext (7.0.4)
|
43
|
+
actionpack (= 7.0.4)
|
44
|
+
activerecord (= 7.0.4)
|
45
|
+
activestorage (= 7.0.4)
|
46
|
+
activesupport (= 7.0.4)
|
47
|
+
globalid (>= 0.6.0)
|
48
|
+
nokogiri (>= 1.8.5)
|
49
|
+
actionview (7.0.4)
|
50
|
+
activesupport (= 7.0.4)
|
19
51
|
builder (~> 3.1)
|
20
52
|
erubi (~> 1.4)
|
21
53
|
rails-dom-testing (~> 2.0)
|
22
|
-
rails-html-sanitizer (~> 1.
|
23
|
-
|
24
|
-
activesupport (=
|
25
|
-
|
26
|
-
|
27
|
-
activesupport (=
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
54
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
55
|
+
activejob (7.0.4)
|
56
|
+
activesupport (= 7.0.4)
|
57
|
+
globalid (>= 0.3.6)
|
58
|
+
activemodel (7.0.4)
|
59
|
+
activesupport (= 7.0.4)
|
60
|
+
activerecord (7.0.4)
|
61
|
+
activemodel (= 7.0.4)
|
62
|
+
activesupport (= 7.0.4)
|
63
|
+
activestorage (7.0.4)
|
64
|
+
actionpack (= 7.0.4)
|
65
|
+
activejob (= 7.0.4)
|
66
|
+
activerecord (= 7.0.4)
|
67
|
+
activesupport (= 7.0.4)
|
68
|
+
marcel (~> 1.0)
|
69
|
+
mini_mime (>= 1.1.0)
|
70
|
+
activesupport (7.0.4)
|
34
71
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
35
|
-
i18n (>=
|
36
|
-
minitest (
|
37
|
-
tzinfo (~>
|
38
|
-
|
39
|
-
|
72
|
+
i18n (>= 1.6, < 2)
|
73
|
+
minitest (>= 5.1)
|
74
|
+
tzinfo (~> 2.0)
|
75
|
+
aws-eventstream (1.2.0)
|
76
|
+
aws-partitions (1.664.0)
|
77
|
+
aws-sdk-core (3.168.1)
|
78
|
+
aws-eventstream (~> 1, >= 1.0.2)
|
79
|
+
aws-partitions (~> 1, >= 1.651.0)
|
80
|
+
aws-sigv4 (~> 1.5)
|
81
|
+
jmespath (~> 1, >= 1.6.1)
|
82
|
+
aws-sdk-kms (1.59.0)
|
83
|
+
aws-sdk-core (~> 3, >= 3.165.0)
|
84
|
+
aws-sigv4 (~> 1.1)
|
85
|
+
aws-sdk-s3 (1.117.1)
|
86
|
+
aws-sdk-core (~> 3, >= 3.165.0)
|
87
|
+
aws-sdk-kms (~> 1)
|
88
|
+
aws-sigv4 (~> 1.4)
|
89
|
+
aws-sigv4 (1.5.2)
|
90
|
+
aws-eventstream (~> 1, >= 1.0.2)
|
40
91
|
builder (3.2.4)
|
92
|
+
coderay (1.1.3)
|
41
93
|
concurrent-ruby (1.1.10)
|
42
94
|
crass (1.0.6)
|
43
95
|
diff-lcs (1.5.0)
|
44
96
|
erubi (1.11.0)
|
97
|
+
ffi (1.15.5)
|
98
|
+
formatador (1.1.0)
|
99
|
+
globalid (1.0.0)
|
100
|
+
activesupport (>= 5.0)
|
101
|
+
guard (2.18.0)
|
102
|
+
formatador (>= 0.2.4)
|
103
|
+
listen (>= 2.7, < 4.0)
|
104
|
+
lumberjack (>= 1.0.12, < 2.0)
|
105
|
+
nenv (~> 0.1)
|
106
|
+
notiffany (~> 0.0)
|
107
|
+
pry (>= 0.13.0)
|
108
|
+
shellany (~> 0.0)
|
109
|
+
thor (>= 0.18.1)
|
110
|
+
guard-compat (1.2.1)
|
111
|
+
guard-rspec (4.7.3)
|
112
|
+
guard (~> 2.1)
|
113
|
+
guard-compat (~> 1.1)
|
114
|
+
rspec (>= 2.99.0, < 4.0)
|
45
115
|
i18n (1.12.0)
|
46
116
|
concurrent-ruby (~> 1.0)
|
47
|
-
|
117
|
+
jmespath (1.6.1)
|
118
|
+
listen (3.7.1)
|
119
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
120
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
121
|
+
loofah (2.19.0)
|
48
122
|
crass (~> 1.0.2)
|
49
123
|
nokogiri (>= 1.5.9)
|
124
|
+
lumberjack (1.2.8)
|
125
|
+
mail (2.7.1)
|
126
|
+
mini_mime (>= 0.1.1)
|
50
127
|
marcel (1.0.2)
|
128
|
+
method_source (1.0.0)
|
129
|
+
mini_mime (1.1.2)
|
51
130
|
mini_portile2 (2.8.0)
|
52
|
-
minitest (5.16.
|
53
|
-
|
131
|
+
minitest (5.16.3)
|
132
|
+
nenv (0.3.0)
|
133
|
+
net-imap (0.3.1)
|
134
|
+
net-protocol
|
135
|
+
net-pop (0.1.2)
|
136
|
+
net-protocol
|
137
|
+
net-protocol (0.1.3)
|
138
|
+
timeout
|
139
|
+
net-smtp (0.3.3)
|
140
|
+
net-protocol
|
141
|
+
nio4r (2.5.8)
|
142
|
+
nokogiri (1.13.9)
|
54
143
|
mini_portile2 (~> 2.8.0)
|
55
144
|
racc (~> 1.4)
|
56
|
-
|
57
|
-
|
58
|
-
|
145
|
+
notiffany (0.1.3)
|
146
|
+
nenv (~> 0.1)
|
147
|
+
shellany (~> 0.0)
|
148
|
+
propshaft (0.6.4)
|
149
|
+
actionpack (>= 7.0.0)
|
150
|
+
activesupport (>= 7.0.0)
|
151
|
+
rack
|
152
|
+
railties (>= 7.0.0)
|
153
|
+
pry (0.14.1)
|
154
|
+
coderay (~> 1.1)
|
155
|
+
method_source (~> 1.0)
|
59
156
|
racc (1.6.0)
|
60
157
|
rack (2.2.4)
|
61
158
|
rack-test (2.0.2)
|
62
159
|
rack (>= 1.3)
|
160
|
+
rails (7.0.4)
|
161
|
+
actioncable (= 7.0.4)
|
162
|
+
actionmailbox (= 7.0.4)
|
163
|
+
actionmailer (= 7.0.4)
|
164
|
+
actionpack (= 7.0.4)
|
165
|
+
actiontext (= 7.0.4)
|
166
|
+
actionview (= 7.0.4)
|
167
|
+
activejob (= 7.0.4)
|
168
|
+
activemodel (= 7.0.4)
|
169
|
+
activerecord (= 7.0.4)
|
170
|
+
activestorage (= 7.0.4)
|
171
|
+
activesupport (= 7.0.4)
|
172
|
+
bundler (>= 1.15.0)
|
173
|
+
railties (= 7.0.4)
|
63
174
|
rails-dom-testing (2.0.3)
|
64
175
|
activesupport (>= 4.2.0)
|
65
176
|
nokogiri (>= 1.6)
|
66
177
|
rails-html-sanitizer (1.4.3)
|
67
178
|
loofah (~> 2.3)
|
68
|
-
|
179
|
+
railties (7.0.4)
|
180
|
+
actionpack (= 7.0.4)
|
181
|
+
activesupport (= 7.0.4)
|
182
|
+
method_source
|
183
|
+
rake (>= 12.2)
|
184
|
+
thor (~> 1.0)
|
185
|
+
zeitwerk (~> 2.5)
|
69
186
|
rake (13.0.6)
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
rspec-
|
75
|
-
rspec-
|
76
|
-
|
77
|
-
|
78
|
-
|
187
|
+
rb-fsevent (0.11.2)
|
188
|
+
rb-inotify (0.10.1)
|
189
|
+
ffi (~> 1.0)
|
190
|
+
rspec (3.12.0)
|
191
|
+
rspec-core (~> 3.12.0)
|
192
|
+
rspec-expectations (~> 3.12.0)
|
193
|
+
rspec-mocks (~> 3.12.0)
|
194
|
+
rspec-core (3.12.0)
|
195
|
+
rspec-support (~> 3.12.0)
|
196
|
+
rspec-expectations (3.12.0)
|
79
197
|
diff-lcs (>= 1.2.0, < 2.0)
|
80
|
-
rspec-support (~> 3.
|
81
|
-
rspec-mocks (3.
|
198
|
+
rspec-support (~> 3.12.0)
|
199
|
+
rspec-mocks (3.12.0)
|
82
200
|
diff-lcs (>= 1.2.0, < 2.0)
|
83
|
-
rspec-support (~> 3.
|
84
|
-
rspec-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
tzinfo (
|
99
|
-
|
100
|
-
|
201
|
+
rspec-support (~> 3.12.0)
|
202
|
+
rspec-rails (5.1.2)
|
203
|
+
actionpack (>= 5.2)
|
204
|
+
activesupport (>= 5.2)
|
205
|
+
railties (>= 5.2)
|
206
|
+
rspec-core (~> 3.10)
|
207
|
+
rspec-expectations (~> 3.10)
|
208
|
+
rspec-mocks (~> 3.10)
|
209
|
+
rspec-support (~> 3.10)
|
210
|
+
rspec-support (3.12.0)
|
211
|
+
shellany (0.0.1)
|
212
|
+
sqlite3 (1.5.4)
|
213
|
+
mini_portile2 (~> 2.8.0)
|
214
|
+
thor (1.2.1)
|
215
|
+
timeout (0.3.0)
|
216
|
+
tzinfo (2.0.5)
|
217
|
+
concurrent-ruby (~> 1.0)
|
218
|
+
websocket-driver (0.7.5)
|
219
|
+
websocket-extensions (>= 0.1.0)
|
220
|
+
websocket-extensions (0.1.5)
|
221
|
+
zeitwerk (2.6.6)
|
101
222
|
|
102
223
|
PLATFORMS
|
103
224
|
ruby
|
104
225
|
|
105
226
|
DEPENDENCIES
|
106
227
|
activestorage_saas!
|
107
|
-
|
108
|
-
rspec (~>
|
109
|
-
|
228
|
+
aws-sdk-s3 (~> 1.114)
|
229
|
+
guard-rspec (~> 4.7)
|
230
|
+
propshaft
|
231
|
+
rails (~> 7.0)
|
232
|
+
rspec-rails (~> 5.1)
|
233
|
+
sqlite3
|
110
234
|
|
111
235
|
BUNDLED WITH
|
112
|
-
2.3.
|
236
|
+
2.3.17
|
data/Guardfile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
end
|
data/Rakefile
CHANGED
@@ -1,12 +1,8 @@
|
|
1
|
-
|
1
|
+
require "bundler/setup"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
RSpec::Core::RakeTask.new(:spec)
|
3
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
4
|
+
load "rails/tasks/engine.rake"
|
7
5
|
|
8
|
-
|
6
|
+
load "rails/tasks/statistics.rake"
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
task default: %i[spec rubocop]
|
8
|
+
require "bundler/gem_tasks"
|
data/activestorage_saas.gemspec
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
activestorage_version = '5.2.5'
|
4
|
-
gem_version = 2
|
5
|
-
|
6
3
|
Gem::Specification.new do |spec|
|
7
4
|
spec.name = "activestorage_saas"
|
8
|
-
spec.version = "
|
5
|
+
spec.version = "7.0.4"
|
9
6
|
spec.authors = ["xiaohui"]
|
10
7
|
spec.email = ["xiaohui@tanmer.com"]
|
11
8
|
|
@@ -29,5 +26,5 @@ Gem::Specification.new do |spec|
|
|
29
26
|
|
30
27
|
spec.require_paths = ["lib"]
|
31
28
|
|
32
|
-
spec.add_dependency "activestorage",
|
29
|
+
spec.add_dependency "activestorage", '>=7.0.3.1', '<= 7.0.4'
|
33
30
|
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
Rails.application.configure do
|
2
|
+
# config.active_storage_saas.service_resolver = ->(blob) { StorageServiceConfiguration.from_service_name(blob.service_name)&.to_service }
|
3
|
+
# config.active_storage_saas.service_name_converter = ->(controller) { controller.send(:current_tenant).storage_service&.to_service_name }
|
4
|
+
# config.active_storage_saas.service_name_validator = ->(service_name) { StorageServiceConfiguration.valid_service_name?(service_name) }
|
5
|
+
# config.active_storage_saas.direct_upload_extra_blob_args = ->(controller) { { tenant: controller.send(:current_tenant) } }
|
6
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'ruby2_keywords'
|
1
2
|
module ActiveStorage
|
2
3
|
class Service
|
3
4
|
# # config/storage.yml
|
@@ -22,25 +23,32 @@ module ActiveStorage
|
|
22
23
|
end
|
23
24
|
|
24
25
|
Service.public_instance_methods(false).each do |name|
|
26
|
+
next if name.in?(%i[name=])
|
27
|
+
|
25
28
|
define_method name do |*args, &block|
|
26
29
|
service.public_send(name, *args, &block)
|
27
30
|
end
|
31
|
+
ruby2_keywords name
|
32
|
+
end
|
33
|
+
|
34
|
+
def name=(_)
|
35
|
+
# do nothing, name should be service.name
|
28
36
|
end
|
29
37
|
|
30
38
|
redefine_method :url_expires_in do
|
31
39
|
@options.fetch(:url_expires_in, 5.minutes)
|
32
40
|
end
|
33
41
|
|
34
|
-
def method_missing(name, *args, &block)
|
42
|
+
ruby2_keywords def method_missing(name, *args, &block)
|
35
43
|
service.public_send(name, *args, &block)
|
36
44
|
end
|
37
45
|
|
38
|
-
def respond_to_missing?(method)
|
46
|
+
def respond_to_missing?(method, _include_all)
|
39
47
|
service.respond_to?(method)
|
40
48
|
end
|
41
49
|
|
42
50
|
def http_method_for_direct_upload
|
43
|
-
service.
|
51
|
+
service.http_method_for_direct_upload if service.respond_to?(:http_method_for_direct_upload)
|
44
52
|
end
|
45
53
|
|
46
54
|
def http_response_type_for_direct_upload
|
@@ -65,15 +73,12 @@ module ActiveStorage
|
|
65
73
|
end
|
66
74
|
|
67
75
|
def service
|
68
|
-
@service ||= blob
|
76
|
+
@service ||= (blob && ActiveStorageSaas.service_resolver.call(blob)) || default_service
|
69
77
|
end
|
70
78
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
@default_service ||= ActiveStorage::Service.configure @options[:default_service],
|
75
|
-
Rails.configuration.active_storage.service_configurations
|
76
|
-
end
|
79
|
+
def default_service
|
80
|
+
@default_service ||= ActiveStorage::Blob.services.fetch(@options[:default_service])
|
81
|
+
end
|
77
82
|
end
|
78
83
|
end
|
79
84
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'active_storage/service/saas_service'
|
2
|
+
|
3
|
+
module ActiveStorageSaas
|
4
|
+
module BlobModelMixin
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
prepend InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
prepended do
|
12
|
+
prepend InstanceMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
|
18
|
+
prepended do
|
19
|
+
redefine_method :service do
|
20
|
+
@service ||= begin
|
21
|
+
raise KeyError, "storage service name should not be 'saas'" if service_name.to_s == 'saas'
|
22
|
+
|
23
|
+
services.fetch(service_name) { |_| ActiveStorageSaas.service_resolver.call(self) } || global_service
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
validate on: :save do
|
28
|
+
errors.add(:service_name, :invalid) if service_name.to_s == 'saas'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def global_service
|
35
|
+
self.class.service
|
36
|
+
end
|
37
|
+
|
38
|
+
def analyzer_class
|
39
|
+
analyzers = service.class.respond_to?(:analyzers) ? service.class.analyzers : ActiveStorage.analyzers
|
40
|
+
analyzers.detect { |klass| klass.accept?(self) } || ActiveStorage::Analyzer::NullAnalyzer
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_service_name_in_services
|
44
|
+
ActiveStorageSaas.service_name_validator.call(service_name) || super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ActiveStorageSaas
|
2
|
+
module DirectUploadsControllerMixin
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
prepend InstanceMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
prepended do
|
10
|
+
prepend InstanceMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
def create
|
15
|
+
blob = ActiveStorage::Blob.create!(blob_args)
|
16
|
+
render json: direct_upload_json(blob)
|
17
|
+
end
|
18
|
+
|
19
|
+
def callback
|
20
|
+
ActiveStorageSaas.direct_upload_callback.call(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def blob_args
|
26
|
+
super.merge(ActiveStorageSaas.direct_upload_extra_blob_args.call(self))
|
27
|
+
.merge(service_name: ActiveStorageSaas.service_name_converter.call(self))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,17 +1,38 @@
|
|
1
|
+
require 'active_storage_saas/blob_model_mixin'
|
2
|
+
require 'active_storage_saas/direct_uploads_controller_mixin'
|
3
|
+
require 'active_storage_saas/storage_service_configuration_model_mixin'
|
1
4
|
module ActiveStorageSaas
|
2
5
|
class Engine < Rails::Engine
|
3
|
-
config.
|
4
|
-
|
5
|
-
|
6
|
+
config.generators do |g|
|
7
|
+
g.test_framework :rspec
|
8
|
+
end
|
9
|
+
config.active_storage_saas = ActiveSupport::OrderedOptions.new
|
10
|
+
|
11
|
+
initializer "active_storage_saas.configs" do
|
12
|
+
config.after_initialize do |app|
|
13
|
+
default_service_resolver = ->(blob) { StorageServiceConfiguration.from_service_name(blob.service_name)&.to_service }
|
14
|
+
default_service_name_converter = ->(controller) { StorageServiceConfiguration.first.to_service_name }
|
15
|
+
default_service_name_validator = ->(service_name) { StorageServiceConfiguration.valid_service_name?(service_name) }
|
16
|
+
default_direct_upload_extra_blob_args = ->(controller) { { } }
|
17
|
+
ActiveStorageSaas.service_resolver = app.config.active_storage_saas.service_resolver || default_service_resolver
|
18
|
+
ActiveStorageSaas.service_name_converter = app.config.active_storage_saas.service_name_converter || default_service_name_converter
|
19
|
+
ActiveStorageSaas.service_name_validator = app.config.active_storage_saas.service_name_validator || default_service_name_validator
|
20
|
+
ActiveStorageSaas.direct_upload_extra_blob_args = app.config.active_storage_saas.direct_upload_extra_blob_args || default_direct_upload_extra_blob_args
|
21
|
+
ActiveStorage::Blob # call this class to load mixin
|
22
|
+
end
|
23
|
+
|
24
|
+
config.to_prepare do
|
25
|
+
ActiveStorage::DirectUploadsController.include ActiveStorageSaas::DirectUploadsControllerMixin
|
26
|
+
end
|
6
27
|
end
|
7
28
|
|
8
29
|
config.to_prepare do
|
9
30
|
ActionDispatch::Routing::Mapper.include Routes
|
10
31
|
end
|
11
32
|
|
12
|
-
initializer '
|
33
|
+
initializer 'active_storage_saas.load_mixins' do
|
13
34
|
ActiveSupport.on_load(:active_storage_blob) do
|
14
|
-
include ActiveStorageSaas::
|
35
|
+
include ActiveStorageSaas::BlobModelMixin
|
15
36
|
end
|
16
37
|
end
|
17
38
|
end
|
@@ -3,9 +3,9 @@ module ActiveStorageSaas
|
|
3
3
|
# rubocop: disable Layout/LineLength
|
4
4
|
def draw_active_storage_saas_routes(
|
5
5
|
prefix: '/rails/active_storage',
|
6
|
-
blobs_controller: '
|
7
|
-
representations_controller: '
|
8
|
-
disk_controller: '
|
6
|
+
blobs_controller: 'active_storage_saas/blobs',
|
7
|
+
representations_controller: 'active_storage_saas/representations',
|
8
|
+
disk_controller: 'active_storage_saas/disk',
|
9
9
|
direct_uploads_controller: 'active_storage_saas/direct_uploads',
|
10
10
|
**option_overrides
|
11
11
|
)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module ActiveStorageSaas
|
2
|
+
module StorageServiceConfigurationMixin
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
prepend InstanceMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
prepended do
|
10
|
+
prepend InstanceMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
extend ActiveSupport::Concern
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
def service_name_pattern
|
18
|
+
/^#{name}:(\d+)$/
|
19
|
+
end
|
20
|
+
|
21
|
+
def from_service_name(service_name)
|
22
|
+
find_by(id: Regexp.last_match(1)) if service_name =~ service_name_pattern
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid_service_name?(service_name)
|
26
|
+
service_name_pattern.match?(service_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_service
|
31
|
+
defined_service = ActiveStorage::Blob.services.fetch(service_name)
|
32
|
+
klass = defined_service.class
|
33
|
+
options = ActiveStorage::Blob.services.send(:configurations).fetch(service_name.to_sym)
|
34
|
+
options.deep_merge! service_options.deep_symbolize_keys
|
35
|
+
klass.new(**options).tap do |instance|
|
36
|
+
instance.instance_eval <<~RUBY
|
37
|
+
define_singleton_method(:name) { "#{to_service_name}" }
|
38
|
+
RUBY
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_service_name
|
43
|
+
"#{self.class.name}:#{id}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/active_storage_saas.rb
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
module ActiveStorageSaas
|
4
4
|
class Error < StandardError; end
|
5
5
|
|
6
|
-
mattr_accessor :
|
6
|
+
mattr_accessor :service_resolver
|
7
|
+
mattr_accessor :service_name_converter
|
8
|
+
mattr_accessor :service_name_validator
|
9
|
+
mattr_accessor :direct_upload_extra_blob_args
|
7
10
|
|
8
11
|
require 'active_storage_saas/routes'
|
9
|
-
require 'active_storage_saas/blob_patch'
|
10
12
|
require 'active_storage_saas/engine'
|
11
13
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveStorageSaas
|
2
|
+
class InstallGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path("templates", __dir__)
|
4
|
+
class_option :configuration_model, type: :string, default: 'StorageServiceConfiguration'
|
5
|
+
|
6
|
+
def generate_model
|
7
|
+
generate "model", "#{options['configuration_model']} service_name service_options:json"
|
8
|
+
inject_into_file "app/models/#{options['configuration_model'].underscore}.rb", after: /^class .+\n/ do <<-'RUBY'
|
9
|
+
include ActiveStorageSaas::StorageServiceConfigurationMixin
|
10
|
+
|
11
|
+
RUBY
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def copy_files
|
16
|
+
template 'config/initializers/active_storage_saas.rb'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/generators/active_storage_saas/install/templates/config/initializers/active_storage_saas.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
Rails.application.configure do
|
2
|
+
# config.active_storage_saas.service_resolver = ->(blob) { StorageServiceConfiguration.from_service_name(blob.service_name)&.to_service }
|
3
|
+
# config.active_storage_saas.service_name_converter = ->(controller) { controller.send(:current_tenant).storage_service&.to_service_name }
|
4
|
+
# config.active_storage_saas.service_name_validator = ->(service_name) { StorageServiceConfiguration.valid_service_name?(service_name) }
|
5
|
+
# config.active_storage_saas.direct_upload_extra_blob_args = ->(controller) { { tenant: controller.send(:current_tenant) } }
|
6
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activestorage_saas
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 7.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- xiaohui
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activestorage
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 7.0.3.1
|
20
|
+
- - "<="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 7.0.4
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- -
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 7.0.3.1
|
30
|
+
- - "<="
|
25
31
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
32
|
+
version: 7.0.4
|
27
33
|
description: Each tenant can set its own storage service, ActiveStorage service can
|
28
34
|
be dynamically loaded.
|
29
35
|
email:
|
@@ -37,25 +43,23 @@ files:
|
|
37
43
|
- CHANGELOG.md
|
38
44
|
- Gemfile
|
39
45
|
- Gemfile.lock
|
46
|
+
- Guardfile
|
40
47
|
- LICENSE.txt
|
41
48
|
- README.md
|
42
49
|
- Rakefile
|
43
50
|
- activestorage_saas.gemspec
|
44
|
-
-
|
45
|
-
- app/javascript/active_storage_saas/direct_upload_controller.js
|
46
|
-
- app/javascript/active_storage_saas/direct_upload_controller/blob_record.js
|
47
|
-
- app/javascript/active_storage_saas/direct_upload_controller/blob_upload.js
|
48
|
-
- app/javascript/active_storage_saas/direct_upload_controller/direct_upload.js
|
49
|
-
- app/javascript/active_storage_saas/direct_upload_controller/file_checksum.js
|
50
|
-
- app/javascript/active_storage_saas/direct_upload_controller/helpers.js
|
51
|
-
- app/models/tenant_storage_service.rb
|
52
|
-
- db/migrate/001_activestorage_saas_tables.rb
|
51
|
+
- config/initializers/active_storage_saas.rb
|
53
52
|
- lib/active_storage/service/saas_service.rb
|
54
53
|
- lib/active_storage_saas.rb
|
55
|
-
- lib/active_storage_saas/
|
54
|
+
- lib/active_storage_saas/blob_model_mixin.rb
|
55
|
+
- lib/active_storage_saas/direct_uploads_controller_mixin.rb
|
56
56
|
- lib/active_storage_saas/engine.rb
|
57
57
|
- lib/active_storage_saas/routes.rb
|
58
|
+
- lib/active_storage_saas/storage_service_configuration_model_mixin.rb
|
58
59
|
- lib/activestorage_saas.rb
|
60
|
+
- lib/generators/active_storage_saas/install/USAGE
|
61
|
+
- lib/generators/active_storage_saas/install/install_generator.rb
|
62
|
+
- lib/generators/active_storage_saas/install/templates/config/initializers/active_storage_saas.rb
|
59
63
|
- sig/activestorage_saas.rbs
|
60
64
|
homepage: https://github.com/xiaohui-zhangxh/activestorage_saas
|
61
65
|
licenses:
|
@@ -1,28 +0,0 @@
|
|
1
|
-
class ActiveStorageSaas::DirectUploadsController < ActiveStorage::BaseController
|
2
|
-
def create
|
3
|
-
blob = ActiveStorage::Blob.create!(blob_args)
|
4
|
-
render json: direct_upload_json(blob)
|
5
|
-
end
|
6
|
-
|
7
|
-
def callback
|
8
|
-
raise NotImplementedError
|
9
|
-
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def blob_args
|
14
|
-
blob_args = params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type).to_h.symbolize_keys
|
15
|
-
blob_args.merge! tenant: current_tenant, tenant_storage_service: current_tenant.storage_service
|
16
|
-
end
|
17
|
-
|
18
|
-
def direct_upload_json(blob)
|
19
|
-
blob.as_json(root: false, methods: :signed_id, only: :signed_id)
|
20
|
-
.merge(direct_upload: {
|
21
|
-
url: blob.service_url_for_direct_upload,
|
22
|
-
method: blob.service_http_method_for_direct_upload,
|
23
|
-
responseType: blob.service_http_response_type_for_direct_upload,
|
24
|
-
headers: blob.service_headers_for_direct_upload,
|
25
|
-
formData: blob.service_form_data_for_direct_upload.presence
|
26
|
-
})
|
27
|
-
end
|
28
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
import { getMetaValue } from "./helpers"
|
2
|
-
|
3
|
-
export class BlobRecord {
|
4
|
-
constructor(file, checksum, url) {
|
5
|
-
this.file = file
|
6
|
-
|
7
|
-
this.attributes = {
|
8
|
-
filename: file.name,
|
9
|
-
content_type: file.type || "application/octet-stream",
|
10
|
-
byte_size: file.size,
|
11
|
-
checksum: checksum
|
12
|
-
}
|
13
|
-
|
14
|
-
this.xhr = new XMLHttpRequest
|
15
|
-
this.xhr.open("POST", url, true)
|
16
|
-
this.xhr.responseType = "json"
|
17
|
-
this.xhr.setRequestHeader("Content-Type", "application/json")
|
18
|
-
this.xhr.setRequestHeader("Accept", "application/json")
|
19
|
-
this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
20
|
-
|
21
|
-
const csrfToken = getMetaValue("csrf-token")
|
22
|
-
if (csrfToken != undefined) {
|
23
|
-
this.xhr.setRequestHeader("X-CSRF-Token", csrfToken)
|
24
|
-
}
|
25
|
-
|
26
|
-
this.xhr.addEventListener("load", event => this.requestDidLoad(event))
|
27
|
-
this.xhr.addEventListener("error", event => this.requestDidError(event))
|
28
|
-
}
|
29
|
-
|
30
|
-
get status() {
|
31
|
-
return this.xhr.status
|
32
|
-
}
|
33
|
-
|
34
|
-
get response() {
|
35
|
-
const { responseType, response } = this.xhr
|
36
|
-
if (responseType == "json") {
|
37
|
-
return response
|
38
|
-
} else {
|
39
|
-
// Shim for IE 11: https://connect.microsoft.com/IE/feedback/details/794808
|
40
|
-
return JSON.parse(response)
|
41
|
-
}
|
42
|
-
}
|
43
|
-
|
44
|
-
create(callback) {
|
45
|
-
this.callback = callback
|
46
|
-
this.xhr.send(JSON.stringify({ blob: this.attributes }))
|
47
|
-
}
|
48
|
-
|
49
|
-
requestDidLoad(event) {
|
50
|
-
if (this.status >= 200 && this.status < 300) {
|
51
|
-
const { response } = this
|
52
|
-
const { direct_upload } = response
|
53
|
-
delete response.direct_upload
|
54
|
-
this.attributes = response
|
55
|
-
this.directUploadData = direct_upload
|
56
|
-
this.callback(null, this.toJSON())
|
57
|
-
} else {
|
58
|
-
this.requestDidError(event)
|
59
|
-
}
|
60
|
-
}
|
61
|
-
|
62
|
-
requestDidError(event) {
|
63
|
-
this.callback(event, this)
|
64
|
-
}
|
65
|
-
|
66
|
-
toJSON() {
|
67
|
-
const result = {}
|
68
|
-
for (const key in this.attributes) {
|
69
|
-
result[key] = this.attributes[key]
|
70
|
-
}
|
71
|
-
return result
|
72
|
-
}
|
73
|
-
}
|
@@ -1,45 +0,0 @@
|
|
1
|
-
export class BlobUpload {
|
2
|
-
constructor(blob) {
|
3
|
-
this.blob = blob
|
4
|
-
this.file = blob.file
|
5
|
-
|
6
|
-
const { url, headers, method, responseType } = blob.directUploadData
|
7
|
-
|
8
|
-
this.xhr = new XMLHttpRequest
|
9
|
-
this.xhr.open(method || "PUT", url, true)
|
10
|
-
this.xhr.responseType = responseType || "text"
|
11
|
-
for (const key in headers) {
|
12
|
-
this.xhr.setRequestHeader(key, headers[key])
|
13
|
-
}
|
14
|
-
this.xhr.addEventListener("load", event => this.requestDidLoad(event))
|
15
|
-
this.xhr.addEventListener("error", event => this.requestDidError(event))
|
16
|
-
}
|
17
|
-
|
18
|
-
create(callback) {
|
19
|
-
this.callback = callback
|
20
|
-
if(this.blob.directUploadData.formData){
|
21
|
-
var formData
|
22
|
-
formData = new FormData()
|
23
|
-
for(const key in this.blob.directUploadData.formData){
|
24
|
-
formData.append(key, this.blob.directUploadData.formData[key])
|
25
|
-
}
|
26
|
-
formData.append('file', this.file)
|
27
|
-
this.xhr.send(formData)
|
28
|
-
}else{
|
29
|
-
this.xhr.send(this.file.slice())
|
30
|
-
}
|
31
|
-
}
|
32
|
-
|
33
|
-
requestDidLoad(event) {
|
34
|
-
const { status, response } = this.xhr
|
35
|
-
if (status >= 200 && status < 300) {
|
36
|
-
this.callback(null, response)
|
37
|
-
} else {
|
38
|
-
this.requestDidError(event)
|
39
|
-
}
|
40
|
-
}
|
41
|
-
|
42
|
-
requestDidError(event) {
|
43
|
-
this.callback(event, this)
|
44
|
-
}
|
45
|
-
}
|
@@ -1,48 +0,0 @@
|
|
1
|
-
import { FileChecksum } from "./file_checksum"
|
2
|
-
import { BlobRecord } from "./blob_record"
|
3
|
-
import { BlobUpload } from "./blob_upload"
|
4
|
-
|
5
|
-
let id = 0
|
6
|
-
|
7
|
-
export class DirectUpload {
|
8
|
-
constructor(file, url, delegate) {
|
9
|
-
this.id = ++id
|
10
|
-
this.file = file
|
11
|
-
this.url = url
|
12
|
-
this.delegate = delegate
|
13
|
-
}
|
14
|
-
|
15
|
-
create(callback) {
|
16
|
-
FileChecksum.create(this.file, (error, checksum) => {
|
17
|
-
if (error) {
|
18
|
-
callback(error)
|
19
|
-
return
|
20
|
-
}
|
21
|
-
|
22
|
-
const blob = new BlobRecord(this.file, checksum, this.url)
|
23
|
-
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr)
|
24
|
-
|
25
|
-
blob.create(error => {
|
26
|
-
if (error) {
|
27
|
-
callback(error)
|
28
|
-
} else {
|
29
|
-
const upload = new BlobUpload(blob)
|
30
|
-
notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr)
|
31
|
-
upload.create(error => {
|
32
|
-
if (error) {
|
33
|
-
callback(error)
|
34
|
-
} else {
|
35
|
-
callback(null, blob.toJSON())
|
36
|
-
}
|
37
|
-
})
|
38
|
-
}
|
39
|
-
})
|
40
|
-
})
|
41
|
-
}
|
42
|
-
}
|
43
|
-
|
44
|
-
function notify(object, methodName, ...messages) {
|
45
|
-
if (object && typeof object[methodName] == "function") {
|
46
|
-
return object[methodName](...messages)
|
47
|
-
}
|
48
|
-
}
|
@@ -1,53 +0,0 @@
|
|
1
|
-
import SparkMD5 from "spark-md5"
|
2
|
-
|
3
|
-
const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
|
4
|
-
|
5
|
-
export class FileChecksum {
|
6
|
-
static create(file, callback) {
|
7
|
-
const instance = new FileChecksum(file)
|
8
|
-
instance.create(callback)
|
9
|
-
}
|
10
|
-
|
11
|
-
constructor(file) {
|
12
|
-
this.file = file
|
13
|
-
this.chunkSize = 2097152 // 2MB
|
14
|
-
this.chunkCount = Math.ceil(this.file.size / this.chunkSize)
|
15
|
-
this.chunkIndex = 0
|
16
|
-
}
|
17
|
-
|
18
|
-
create(callback) {
|
19
|
-
this.callback = callback
|
20
|
-
this.md5Buffer = new SparkMD5.ArrayBuffer
|
21
|
-
this.fileReader = new FileReader
|
22
|
-
this.fileReader.addEventListener("load", event => this.fileReaderDidLoad(event))
|
23
|
-
this.fileReader.addEventListener("error", event => this.fileReaderDidError(event))
|
24
|
-
this.readNextChunk()
|
25
|
-
}
|
26
|
-
|
27
|
-
fileReaderDidLoad(event) {
|
28
|
-
this.md5Buffer.append(event.target.result)
|
29
|
-
|
30
|
-
if (!this.readNextChunk()) {
|
31
|
-
const binaryDigest = this.md5Buffer.end(true)
|
32
|
-
const base64digest = btoa(binaryDigest)
|
33
|
-
this.callback(null, base64digest)
|
34
|
-
}
|
35
|
-
}
|
36
|
-
|
37
|
-
fileReaderDidError(event) {
|
38
|
-
this.callback(`Error reading ${this.file.name}`)
|
39
|
-
}
|
40
|
-
|
41
|
-
readNextChunk() {
|
42
|
-
if (this.chunkIndex < this.chunkCount || (this.chunkIndex == 0 && this.chunkCount == 0)) {
|
43
|
-
const start = this.chunkIndex * this.chunkSize
|
44
|
-
const end = Math.min(start + this.chunkSize, this.file.size)
|
45
|
-
const bytes = fileSlice.call(this.file, start, end)
|
46
|
-
this.fileReader.readAsArrayBuffer(bytes)
|
47
|
-
this.chunkIndex++
|
48
|
-
return true
|
49
|
-
} else {
|
50
|
-
return false
|
51
|
-
}
|
52
|
-
}
|
53
|
-
}
|
@@ -1,51 +0,0 @@
|
|
1
|
-
export function getMetaValue(name) {
|
2
|
-
const element = findElement(document.head, `meta[name="${name}"]`)
|
3
|
-
if (element) {
|
4
|
-
return element.getAttribute("content")
|
5
|
-
}
|
6
|
-
}
|
7
|
-
|
8
|
-
export function findElements(root, selector) {
|
9
|
-
if (typeof root == "string") {
|
10
|
-
selector = root
|
11
|
-
root = document
|
12
|
-
}
|
13
|
-
const elements = root.querySelectorAll(selector)
|
14
|
-
return toArray(elements)
|
15
|
-
}
|
16
|
-
|
17
|
-
export function findElement(root, selector) {
|
18
|
-
if (typeof root == "string") {
|
19
|
-
selector = root
|
20
|
-
root = document
|
21
|
-
}
|
22
|
-
return root.querySelector(selector)
|
23
|
-
}
|
24
|
-
|
25
|
-
export function dispatchEvent(element, type, eventInit = {}) {
|
26
|
-
const { disabled } = element
|
27
|
-
const { bubbles, cancelable, detail } = eventInit
|
28
|
-
const event = document.createEvent("Event")
|
29
|
-
|
30
|
-
event.initEvent(type, bubbles || true, cancelable || true)
|
31
|
-
event.detail = detail || {}
|
32
|
-
|
33
|
-
try {
|
34
|
-
element.disabled = false
|
35
|
-
element.dispatchEvent(event)
|
36
|
-
} finally {
|
37
|
-
element.disabled = disabled
|
38
|
-
}
|
39
|
-
|
40
|
-
return event
|
41
|
-
}
|
42
|
-
|
43
|
-
export function toArray(value) {
|
44
|
-
if (Array.isArray(value)) {
|
45
|
-
return value
|
46
|
-
} else if (Array.from) {
|
47
|
-
return Array.from(value)
|
48
|
-
} else {
|
49
|
-
return [].slice.call(value)
|
50
|
-
}
|
51
|
-
}
|
@@ -1,78 +0,0 @@
|
|
1
|
-
import { Controller } from "@hotwired/stimulus";
|
2
|
-
import { DirectUpload } from "./direct_upload_controller/direct_upload";
|
3
|
-
|
4
|
-
const URL = window.URL || window.webkitURL
|
5
|
-
|
6
|
-
function setupFilePreview(target, file){
|
7
|
-
const onLoaded = function(){
|
8
|
-
URL.revokeObjectURL(target.src)
|
9
|
-
target.removeEventListener('load', onLoaded)
|
10
|
-
}
|
11
|
-
target.addEventListener('load', onLoaded)
|
12
|
-
target.src = URL.createObjectURL(file)
|
13
|
-
}
|
14
|
-
export default class extends Controller {
|
15
|
-
static targets = ['file', 'preview'];
|
16
|
-
static values = {
|
17
|
-
url: String,
|
18
|
-
}
|
19
|
-
|
20
|
-
initialize(){
|
21
|
-
this.onFileChange = this.onFileChange.bind(this)
|
22
|
-
}
|
23
|
-
|
24
|
-
fileTargetConnected(target){
|
25
|
-
if(!target.hiddenInput){
|
26
|
-
const hiddenInput = document.createElement("input")
|
27
|
-
hiddenInput.type = "hidden"
|
28
|
-
hiddenInput.name = target.name
|
29
|
-
target.insertAdjacentElement("beforebegin", hiddenInput)
|
30
|
-
target.removeAttribute('name')
|
31
|
-
target.hiddenInput = hiddenInput
|
32
|
-
}
|
33
|
-
target.addEventListener('change', this.onFileChange)
|
34
|
-
}
|
35
|
-
|
36
|
-
fileTargetDisconnected(target){
|
37
|
-
target.removeEventListener('change', this.onFileChange)
|
38
|
-
}
|
39
|
-
|
40
|
-
onFileChange(event){
|
41
|
-
const { target } = event
|
42
|
-
const { hiddenInput, files } = target
|
43
|
-
const file = files[0]
|
44
|
-
if(!file) return
|
45
|
-
|
46
|
-
const directUpload = new DirectUpload(file, this.urlValue, this)
|
47
|
-
|
48
|
-
if(this.hasPreviewTarget){
|
49
|
-
setupFilePreview(this.previewTarget, file)
|
50
|
-
}
|
51
|
-
|
52
|
-
directUpload.create((error, attributes) => {
|
53
|
-
if(error){
|
54
|
-
hiddenInput.removeAttribute('value')
|
55
|
-
}else{
|
56
|
-
hiddenInput.setAttribute('value', attributes.signed_id)
|
57
|
-
}
|
58
|
-
})
|
59
|
-
}
|
60
|
-
|
61
|
-
directUploadWillCreateBlobWithXHR(xhr) {
|
62
|
-
this.dispatch("before-blob-request", { detail: xhr })
|
63
|
-
}
|
64
|
-
|
65
|
-
directUploadWillStoreFileWithXHR(xhr) {
|
66
|
-
this.dispatch('started', { detail: xhr })
|
67
|
-
this.dispatch('progress', { detail: { percent: 0 } })
|
68
|
-
xhr.upload.addEventListener("progress", event => this.uploadRequestDidProgress(event))
|
69
|
-
}
|
70
|
-
|
71
|
-
uploadRequestDidProgress(event){
|
72
|
-
const percent = event.loaded / event.total
|
73
|
-
this.dispatch('progress', { detail: { percent } })
|
74
|
-
if(percent == 1){
|
75
|
-
this.dispatch('uploaded')
|
76
|
-
}
|
77
|
-
}
|
78
|
-
}
|
@@ -1,17 +0,0 @@
|
|
1
|
-
class ActivestorageSaasTables < ActiveRecord::Migration[5.2]
|
2
|
-
def change
|
3
|
-
tenant_class = ActiveStorageSaas.tenant_class_name.constantize
|
4
|
-
tenant_primary_key = tenant_class.columns.find{|col| col.name == tenant_class.primary_key }
|
5
|
-
create_table :tenant_storage_services do |t|
|
6
|
-
t.references :tenant, type: tenant_primary_key.type, foreign_key: { to_table: tenant_class.table_name }
|
7
|
-
t.string :service_name
|
8
|
-
t.json :service_options
|
9
|
-
|
10
|
-
t.timestamps
|
11
|
-
end
|
12
|
-
|
13
|
-
add_reference tenant_class.table_name.to_sym, :tenant_storage_service, foreign_key: true
|
14
|
-
add_reference :active_storage_blobs, :tenant, type: tenant_primary_key.type, foreign_key: true
|
15
|
-
add_reference :active_storage_blobs, :tenant_storage_service, foreign_key: true
|
16
|
-
end
|
17
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'active_storage/service/saas_service'
|
2
|
-
|
3
|
-
module ActiveStorageSaas
|
4
|
-
module BlobPatch
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
included do
|
8
|
-
belongs_to :tenant, class_name: ActiveStorageSaas.tenant_class_name # rubocop: disable Rails/ReflectionClassName
|
9
|
-
belongs_to :tenant_storage_service, optional: true
|
10
|
-
|
11
|
-
redefine_method :service do
|
12
|
-
class_service = self.class.service
|
13
|
-
if class_service.is_a?(ActiveStorage::Service::SaasService)
|
14
|
-
@service ||= ActiveStorage::Service::SaasService.new(class_service.options.merge(blob: self))
|
15
|
-
else
|
16
|
-
class_service
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def service_http_method_for_direct_upload
|
22
|
-
service.respond_to?(:http_method_for_direct_upload) ? service.http_method_for_direct_upload : nil
|
23
|
-
end
|
24
|
-
|
25
|
-
def service_http_response_type_for_direct_upload
|
26
|
-
service.respond_to?(:http_response_type_for_direct_upload) ? service.http_response_type_for_direct_upload : nil
|
27
|
-
end
|
28
|
-
|
29
|
-
# support sending data from form instead of headers
|
30
|
-
def service_form_data_for_direct_upload(expires_in: service.url_expires_in)
|
31
|
-
return {} unless service.respond_to?(:form_data_for_direct_upload)
|
32
|
-
|
33
|
-
service.form_data_for_direct_upload(key,
|
34
|
-
expires_in: expires_in,
|
35
|
-
content_type: content_type,
|
36
|
-
content_length: byte_size,
|
37
|
-
checksum: checksum)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def saas_service?
|
43
|
-
service.is_a?(ActiveStorage::Service::SaasService)
|
44
|
-
end
|
45
|
-
|
46
|
-
def analyzer_class
|
47
|
-
analyzers = saas_service? ? service.analyzers : ActiveStorage.analyzers
|
48
|
-
analyzers.detect { |klass| klass.accept?(self) } || ActiveStorage::Analyzer::NullAnalyzer
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|