activestorage_saas 5.2.5.2 → 7.0.4
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.
- 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
|