mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-06 05:04:35 +08:00
Merge branch 'okriuchykhin-ok_SCI_3539' into features/marvinjs-integration
This commit is contained in:
commit
c9ae9f056e
54 changed files with 859 additions and 308 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -35,6 +35,9 @@ ehthumbs.db
|
|||
# Ignore temporary files
|
||||
public/system/*
|
||||
|
||||
# Ignore ActiveStorage Disc service storage directory
|
||||
storage/
|
||||
|
||||
# Ignore robots.txt
|
||||
public/robots.txt
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ AllCops:
|
|||
- "vendor/**/*"
|
||||
- "db/schema.rb"
|
||||
UseCache: false
|
||||
TargetRubyVersion: 2.5
|
||||
TargetRubyVersion: 2.6
|
||||
|
||||
##################### Style ####################################
|
||||
|
||||
|
@ -91,7 +91,7 @@ Style/FormatString:
|
|||
EnforcedStyle: format
|
||||
|
||||
Style/FrozenStringLiteralComment:
|
||||
EnforcedStyle: when_needed
|
||||
EnforcedStyle: always
|
||||
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM ruby:2.5.5
|
||||
FROM ruby:2.6.3
|
||||
MAINTAINER BioSistemika <info@biosistemika.com>
|
||||
|
||||
# Get version of Debian (lsb_release substitute) and save it to /tmp/lsb_release for further commands
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM ruby:2.5.5
|
||||
FROM ruby:2.6.3
|
||||
MAINTAINER BioSistemika <info@biosistemika.com>
|
||||
|
||||
RUN echo deb "http://http.debian.net/debian stretch-backports main" >> /etc/apt/sources.list
|
||||
|
|
7
Gemfile
7
Gemfile
|
@ -2,9 +2,10 @@
|
|||
|
||||
source 'http://rubygems.org'
|
||||
|
||||
ruby '2.5.5'
|
||||
ruby '2.6.3'
|
||||
|
||||
|
||||
gem 'bootsnap', require: false
|
||||
gem 'webpacker', '~> 3.5'
|
||||
gem 'bootstrap-sass', '~> 3.3.7'
|
||||
gem 'bootstrap_form', '~> 2.7.0'
|
||||
|
@ -13,7 +14,7 @@ gem 'devise_invitable'
|
|||
gem 'figaro'
|
||||
gem 'pg', '~> 0.18'
|
||||
gem 'pg_search' # PostgreSQL full text search
|
||||
gem 'rails', '~> 5.1.7'
|
||||
gem 'rails', '~> 5.2.3'
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
gem 'sanitize', '~> 4.4'
|
||||
gem 'sassc-rails'
|
||||
|
@ -79,7 +80,9 @@ gem 'underscore-rails'
|
|||
gem 'wicked_pdf', '~> 1.1.0'
|
||||
gem 'wkhtmltopdf-heroku'
|
||||
|
||||
gem 'aws-sdk-rails'
|
||||
gem 'aws-sdk-s3'
|
||||
gem 'mini_magick'
|
||||
gem 'paperclip', '~> 6.1' # File attachment, image attachment library
|
||||
gem 'delayed_job_active_record'
|
||||
gem 'devise-async',
|
||||
|
|
219
Gemfile.lock
219
Gemfile.lock
|
@ -42,25 +42,25 @@ GIT
|
|||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.1.7)
|
||||
actionpack (= 5.1.7)
|
||||
actioncable (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.1.7)
|
||||
actionpack (= 5.1.7)
|
||||
actionview (= 5.1.7)
|
||||
activejob (= 5.1.7)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailer (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.1.7)
|
||||
actionview (= 5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
actionpack (5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
actionview (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -70,18 +70,22 @@ GEM
|
|||
activemodel (>= 4.1, < 6)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
activejob (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
activerecord (5.1.7)
|
||||
activemodel (= 5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
arel (~> 8.0)
|
||||
activerecord-import (1.0.1)
|
||||
activemodel (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
activerecord (5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
arel (>= 9.0)
|
||||
activerecord-import (1.0.2)
|
||||
activerecord (>= 3.2)
|
||||
activesupport (5.1.7)
|
||||
activestorage (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (5.2.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
|
@ -91,18 +95,18 @@ GEM
|
|||
aes_key_wrap (1.0.1)
|
||||
ajax-datatables-rails (0.3.1)
|
||||
railties (>= 3.1)
|
||||
arel (8.0.0)
|
||||
arel (9.0.0)
|
||||
aspector (0.14.0)
|
||||
ast (2.4.0)
|
||||
auto_strip_attributes (2.5.0)
|
||||
activerecord (>= 4.0)
|
||||
autoprefixer-rails (9.5.1.1)
|
||||
autoprefixer-rails (9.6.0)
|
||||
execjs
|
||||
autosize-rails (1.18.17)
|
||||
rails (>= 3.1)
|
||||
awesome_print (1.8.0)
|
||||
aws-eventstream (1.0.3)
|
||||
aws-partitions (1.172.0)
|
||||
aws-partitions (1.174.0)
|
||||
aws-sdk-core (3.54.2)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-partitions (~> 1.0)
|
||||
|
@ -111,15 +115,21 @@ GEM
|
|||
aws-sdk-kms (1.21.0)
|
||||
aws-sdk-core (~> 3, >= 3.53.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-rails (2.1.0)
|
||||
aws-sdk-ses (~> 1)
|
||||
railties (>= 3)
|
||||
aws-sdk-s3 (1.42.0)
|
||||
aws-sdk-core (~> 3, >= 3.53.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-ses (1.22.0)
|
||||
aws-sdk-core (~> 3, >= 3.53.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.1.0)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
backports (3.14.0)
|
||||
backports (3.15.0)
|
||||
base62 (1.0.0)
|
||||
bcrypt (3.1.12)
|
||||
bcrypt (3.1.13)
|
||||
better_errors (2.5.1)
|
||||
coderay (>= 1.0.0)
|
||||
erubi (>= 1.0.0)
|
||||
|
@ -127,6 +137,8 @@ GEM
|
|||
bindata (2.4.4)
|
||||
binding_of_caller (0.8.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.4.4)
|
||||
msgpack (~> 1.0)
|
||||
bootstrap-sass (3.3.7)
|
||||
autoprefixer-rails (>= 5.2.1)
|
||||
sass (>= 3.3.4)
|
||||
|
@ -139,13 +151,13 @@ GEM
|
|||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
byebug (11.0.1)
|
||||
capybara (3.18.0)
|
||||
capybara (3.23.0)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (~> 1.2)
|
||||
regexp_parser (~> 1.5)
|
||||
xpath (~> 3.2)
|
||||
capybara-email (3.0.1)
|
||||
capybara (>= 2.4, < 4.0)
|
||||
|
@ -157,9 +169,9 @@ GEM
|
|||
climate_control (0.2.0)
|
||||
cliver (0.3.2)
|
||||
coderay (1.1.2)
|
||||
coffee-rails (4.2.2)
|
||||
coffee-rails (5.0.0)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0)
|
||||
railties (>= 5.2.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
|
@ -193,7 +205,7 @@ GEM
|
|||
cucumber-wire (0.0.1)
|
||||
database_cleaner (1.7.0)
|
||||
debug_inspector (0.0.3)
|
||||
deface (1.3.2)
|
||||
deface (1.4.0)
|
||||
nokogiri (>= 1.6)
|
||||
polyglot
|
||||
rails (>= 4.1)
|
||||
|
@ -213,48 +225,47 @@ GEM
|
|||
actionmailer (>= 5.0)
|
||||
devise (>= 4.6)
|
||||
diff-lcs (1.3)
|
||||
discard (1.0.0)
|
||||
activerecord (>= 4.2, < 6)
|
||||
discard (1.1.0)
|
||||
activerecord (>= 4.2, < 7)
|
||||
docile (1.3.1)
|
||||
doorkeeper (5.1.0)
|
||||
railties (>= 5)
|
||||
erubi (1.8.0)
|
||||
et-orbi (1.2.0)
|
||||
et-orbi (1.2.1)
|
||||
tzinfo
|
||||
execjs (2.7.0)
|
||||
factory_bot (4.8.2)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_bot_rails (4.8.2)
|
||||
factory_bot (~> 4.8.2)
|
||||
railties (>= 3.0.0)
|
||||
faker (1.8.7)
|
||||
factory_bot (5.0.2)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot_rails (5.0.2)
|
||||
factory_bot (~> 5.0.2)
|
||||
railties (>= 4.2.0)
|
||||
faker (1.9.3)
|
||||
i18n (>= 0.7)
|
||||
faraday (0.15.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.10.0)
|
||||
ffi (1.11.1)
|
||||
figaro (1.1.1)
|
||||
thor (~> 0.14)
|
||||
fugit (1.2.0)
|
||||
fugit (1.2.1)
|
||||
et-orbi (~> 1.1, >= 1.1.8)
|
||||
raabro (~> 1.1)
|
||||
gherkin (5.1.0)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
hammerjs-rails (2.0.8)
|
||||
hashdiff (0.3.9)
|
||||
hashdiff (0.4.0)
|
||||
hashie (3.6.0)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (1.6.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-js (3.2.1)
|
||||
i18n-js (3.3.0)
|
||||
i18n (>= 0.6.6)
|
||||
iniparse (1.4.4)
|
||||
jaro_winkler (1.5.2)
|
||||
jbuilder (2.8.0)
|
||||
jbuilder (2.9.1)
|
||||
activesupport (>= 4.2.0)
|
||||
multi_json (>= 1.2)
|
||||
jmespath (1.4.0)
|
||||
jquery-rails (4.3.3)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
|
@ -265,14 +276,14 @@ GEM
|
|||
js_cookie_rails (2.2.0)
|
||||
railties (>= 3.1)
|
||||
json (1.8.6)
|
||||
json-jwt (1.10.0)
|
||||
json-jwt (1.10.1)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
json_matchers (0.11.0)
|
||||
json_schema
|
||||
json_schema (0.20.4)
|
||||
jsonapi-renderer (0.2.0)
|
||||
json_schema (0.20.6)
|
||||
jsonapi-renderer (0.2.1)
|
||||
jwt (1.5.6)
|
||||
kaminari (1.1.1)
|
||||
activesupport (>= 4.1.0)
|
||||
|
@ -300,23 +311,27 @@ GEM
|
|||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
method_source (0.9.2)
|
||||
mime-types (1.25.1)
|
||||
mimemagic (0.3.3)
|
||||
mini_magick (4.9.3)
|
||||
mini_mime (1.0.1)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.11.3)
|
||||
momentjs-rails (2.17.1)
|
||||
railties (>= 3.1)
|
||||
msgpack (1.2.10)
|
||||
multi_json (1.13.1)
|
||||
multi_test (0.1.2)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
multipart-post (2.1.1)
|
||||
nested_form_fields (0.8.2)
|
||||
coffee-rails (>= 3.2.1)
|
||||
jquery-rails
|
||||
rails (>= 3.2.0)
|
||||
newrelic_rpm (6.2.0.354)
|
||||
newrelic_rpm (6.4.0.356)
|
||||
nio4r (2.3.1)
|
||||
nokogiri (1.8.5)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
|
@ -337,7 +352,7 @@ GEM
|
|||
oauth2 (~> 1.1)
|
||||
omniauth (~> 1.9)
|
||||
orm_adapter (0.5.0)
|
||||
overcommit (0.47.0)
|
||||
overcommit (0.48.1)
|
||||
childprocess (~> 0.6, >= 0.6.3)
|
||||
iniparse (~> 1.4)
|
||||
paperclip (6.1.0)
|
||||
|
@ -350,7 +365,7 @@ GEM
|
|||
parser (2.6.3.0)
|
||||
ast (~> 2.4.0)
|
||||
pg (0.21.0)
|
||||
pg_search (2.1.7)
|
||||
pg_search (2.2.0)
|
||||
activerecord (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
phantomjs (2.1.1.0)
|
||||
|
@ -367,7 +382,7 @@ GEM
|
|||
pry (~> 0.10)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.0.3)
|
||||
public_suffix (3.1.0)
|
||||
puma (3.12.1)
|
||||
raabro (1.1.6)
|
||||
rack (2.0.7)
|
||||
|
@ -377,17 +392,18 @@ GEM
|
|||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.1.7)
|
||||
actioncable (= 5.1.7)
|
||||
actionmailer (= 5.1.7)
|
||||
actionpack (= 5.1.7)
|
||||
actionview (= 5.1.7)
|
||||
activejob (= 5.1.7)
|
||||
activemodel (= 5.1.7)
|
||||
activerecord (= 5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
rails (5.2.3)
|
||||
actioncable (= 5.2.3)
|
||||
actionmailer (= 5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
activestorage (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.1.7)
|
||||
railties (= 5.2.3)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.4)
|
||||
actionpack (>= 5.0.1.x)
|
||||
|
@ -405,21 +421,21 @@ GEM
|
|||
rails (> 3.1)
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (5.1.7)
|
||||
actionpack (= 5.1.7)
|
||||
activesupport (= 5.1.7)
|
||||
railties (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
rake (12.3.2)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.0)
|
||||
ffi (~> 1.0)
|
||||
rdoc (6.1.1)
|
||||
recaptcha (4.14.0)
|
||||
recaptcha (5.0.0)
|
||||
json
|
||||
regexp_parser (1.4.0)
|
||||
regexp_parser (1.5.1)
|
||||
responders (2.4.1)
|
||||
actionpack (>= 4.2.0, < 6.0)
|
||||
railties (>= 4.2.0, < 6.0)
|
||||
|
@ -429,36 +445,36 @@ GEM
|
|||
roo (2.8.2)
|
||||
nokogiri (~> 1)
|
||||
rubyzip (>= 1.2.1, < 2.0.0)
|
||||
rspec-core (3.7.1)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-expectations (3.7.0)
|
||||
rspec-core (3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-expectations (3.8.4)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-mocks (3.7.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-mocks (3.8.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-rails (3.7.2)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-rails (3.8.2)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.7.0)
|
||||
rspec-expectations (~> 3.7.0)
|
||||
rspec-mocks (~> 3.7.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-support (3.7.0)
|
||||
rubocop (0.68.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-support (3.8.2)
|
||||
rubocop (0.71.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.5, != 2.5.1.1)
|
||||
parser (>= 2.6)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.6)
|
||||
rubocop-performance (1.2.0)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
rubocop-performance (1.3.0)
|
||||
rubocop (>= 0.68.0)
|
||||
ruby-graphviz (1.2.4)
|
||||
ruby-progressbar (1.10.0)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby_dep (1.5.0)
|
||||
rubyzip (1.2.2)
|
||||
rubyzip (1.2.3)
|
||||
rufus-scheduler (3.6.0)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
safe_yaml (1.0.5)
|
||||
|
@ -485,8 +501,8 @@ GEM
|
|||
railties (>= 4.0.0)
|
||||
sdoc (1.0.0)
|
||||
rdoc (>= 5.0)
|
||||
shoulda-matchers (3.1.2)
|
||||
activesupport (>= 4.0.0)
|
||||
shoulda-matchers (4.1.0)
|
||||
activesupport (>= 4.2.0)
|
||||
silencer (1.0.1)
|
||||
simple_token_authentication (1.15.1)
|
||||
actionmailer (>= 3.2.6, < 6)
|
||||
|
@ -513,7 +529,7 @@ GEM
|
|||
thread_safe (0.3.6)
|
||||
tilt (2.0.9)
|
||||
timecop (0.9.1)
|
||||
tinymce-rails (4.9.3)
|
||||
tinymce-rails (4.9.4)
|
||||
railties (>= 3.1.1)
|
||||
turbolinks (5.1.1)
|
||||
turbolinks-source (~> 5.1)
|
||||
|
@ -523,24 +539,24 @@ GEM
|
|||
uglifier (4.1.20)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
underscore-rails (1.8.3)
|
||||
unicode-display_width (1.5.0)
|
||||
unicode-display_width (1.6.0)
|
||||
uniform_notifier (1.12.1)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
webmock (3.5.1)
|
||||
webmock (3.6.0)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webpacker (3.6.0)
|
||||
activesupport (>= 4.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 4.2)
|
||||
websocket-driver (0.6.5)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.3)
|
||||
websocket-extensions (0.1.4)
|
||||
whacamole (1.2.0)
|
||||
wicked_pdf (1.1.0)
|
||||
wkhtmltopdf-heroku (2.12.4.0)
|
||||
wkhtmltopdf-heroku (2.12.5.0)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
yomu (0.2.4)
|
||||
|
@ -558,11 +574,13 @@ DEPENDENCIES
|
|||
auto_strip_attributes (~> 2.1)
|
||||
autosize-rails
|
||||
awesome_print
|
||||
aws-sdk-rails
|
||||
aws-sdk-s3
|
||||
base62
|
||||
bcrypt (~> 3.1.10)
|
||||
better_errors
|
||||
binding_of_caller
|
||||
bootsnap
|
||||
bootstrap-sass (~> 3.3.7)
|
||||
bootstrap-select-rails (~> 1.12.4)
|
||||
bootstrap3-datetimepicker-rails (~> 4.15.35)
|
||||
|
@ -600,6 +618,7 @@ DEPENDENCIES
|
|||
kaminari
|
||||
listen (~> 3.0)
|
||||
logging (~> 2.0.0)
|
||||
mini_magick
|
||||
momentjs-rails (~> 2.17.1)
|
||||
nested_form_fields
|
||||
newrelic_rpm
|
||||
|
@ -617,7 +636,7 @@ DEPENDENCIES
|
|||
pry-rails
|
||||
puma
|
||||
rack-attack
|
||||
rails (~> 5.1.7)
|
||||
rails (~> 5.2.3)
|
||||
rails-controller-testing
|
||||
rails_12factor
|
||||
rails_autolink (~> 1.1, >= 1.1.6)
|
||||
|
@ -654,7 +673,7 @@ DEPENDENCIES
|
|||
yomu
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.5.5p157
|
||||
ruby 2.6.3p62
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.3
|
||||
|
|
|
@ -47,7 +47,7 @@ class AssetsController < ApplicationController
|
|||
def file_preview
|
||||
response_json = {
|
||||
'id' => @asset.id,
|
||||
'type' => (@asset.is_image? ? 'image' : 'file'),
|
||||
'type' => (@asset.image? ? 'image' : 'file'),
|
||||
|
||||
'filename' => truncate(escape_input(@asset.file_file_name),
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH),
|
||||
|
@ -62,7 +62,7 @@ class AssetsController < ApplicationController
|
|||
can_manage_repository_rows?(@repository.team)
|
||||
end
|
||||
|
||||
if @asset.is_image?
|
||||
if @asset.image?
|
||||
if ['image/jpeg', 'image/pjpeg'].include? @asset.file.content_type
|
||||
response_json['quality'] = @asset.file_image_quality || 90
|
||||
end
|
||||
|
@ -309,7 +309,7 @@ class AssetsController < ApplicationController
|
|||
|
||||
def asset_data_type(asset)
|
||||
return 'wopi' if wopi_file?(asset)
|
||||
return 'image' if asset.is_image?
|
||||
return 'image' if asset.image?
|
||||
|
||||
'file'
|
||||
end
|
||||
|
|
|
@ -6,48 +6,51 @@ class Asset < ApplicationRecord
|
|||
|
||||
require 'tempfile'
|
||||
# Lock duration set to 30 minutes
|
||||
LOCK_DURATION = 60*30
|
||||
LOCK_DURATION = 60 * 30
|
||||
|
||||
# ActiveStorage configuration
|
||||
has_one_attached :file
|
||||
|
||||
# Paperclip validation
|
||||
has_attached_file :file,
|
||||
styles: lambda { |a|
|
||||
if a.previewable_document?
|
||||
{
|
||||
large: { processors: [:custom_file_preview],
|
||||
geometry: Constants::LARGE_PIC_FORMAT,
|
||||
format: :jpg },
|
||||
medium: { processors: [:custom_file_preview],
|
||||
geometry: Constants::MEDIUM_PIC_FORMAT,
|
||||
format: :jpg }
|
||||
}
|
||||
else
|
||||
{
|
||||
large: [Constants::LARGE_PIC_FORMAT, :jpg],
|
||||
medium: [Constants::MEDIUM_PIC_FORMAT, :jpg]
|
||||
}
|
||||
end
|
||||
},
|
||||
convert_options: {
|
||||
medium: '-quality 70 -strip',
|
||||
all: '-background "#d2d2d2" -flatten +matte'
|
||||
}
|
||||
# has_attached_file :file,
|
||||
# styles: lambda { |a|
|
||||
# if a.previewable_document?
|
||||
# {
|
||||
# large: { processors: [:custom_file_preview],
|
||||
# geometry: Constants::LARGE_PIC_FORMAT,
|
||||
# format: :jpg },
|
||||
# medium: { processors: [:custom_file_preview],
|
||||
# geometry: Constants::MEDIUM_PIC_FORMAT,
|
||||
# format: :jpg }
|
||||
# }
|
||||
# else
|
||||
# {
|
||||
# large: [Constants::LARGE_PIC_FORMAT, :jpg],
|
||||
# medium: [Constants::MEDIUM_PIC_FORMAT, :jpg]
|
||||
# }
|
||||
# end
|
||||
# },
|
||||
# convert_options: {
|
||||
# medium: '-quality 70 -strip',
|
||||
# all: '-background "#d2d2d2" -flatten +matte'
|
||||
# }
|
||||
|
||||
before_post_process :previewable?
|
||||
before_post_process :extract_image_quality
|
||||
# before_post_process :previewable?
|
||||
# before_post_process :extract_image_quality
|
||||
|
||||
# adds image processing in background job
|
||||
process_in_background :file, processing_image_url: '/images/:style/processing.gif'
|
||||
# process_in_background :file, processing_image_url: '/images/:style/processing.gif'
|
||||
|
||||
validates_attachment :file,
|
||||
presence: true,
|
||||
size: {
|
||||
less_than: Rails.configuration.x.file_max_size_mb.megabytes
|
||||
}
|
||||
validates :estimated_size, presence: true
|
||||
validates :file_present, inclusion: { in: [true, false] }
|
||||
# validates_attachment :file,
|
||||
# presence: true,
|
||||
# size: {
|
||||
# less_than: Rails.configuration.x.file_max_size_mb.megabytes
|
||||
# }
|
||||
# validates :estimated_size, presence: true
|
||||
# validates :file_present, inclusion: { in: [true, false] }
|
||||
|
||||
# Should be checked for any security leaks
|
||||
do_not_validate_attachment_file_type :file
|
||||
# do_not_validate_attachment_file_type :file
|
||||
|
||||
# Asset validation
|
||||
# This could cause some problems if you create empty asset and want to
|
||||
|
@ -200,6 +203,32 @@ class Asset < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def previewable?
|
||||
previewable_document? || previewable_image?
|
||||
end
|
||||
|
||||
def medium_preview
|
||||
return file.variant(resize: Constants::MEDIUM_PIC_FORMAT) if previewable_image?
|
||||
|
||||
'medium/processing.gif'
|
||||
# file.preview(resize: Constants::MEDIUM_PIC_FORMAT)
|
||||
end
|
||||
|
||||
def large_preview
|
||||
return file.variant(resize: Constants::LARGE_PIC_FORMAT) if previewable_image?
|
||||
|
||||
'large/processing.gif'
|
||||
# file.preview(resize: Constants::LARGE_PIC_FORMAT)
|
||||
end
|
||||
|
||||
def file_name
|
||||
file.blob&.filename&.to_s
|
||||
end
|
||||
|
||||
def file_size
|
||||
file.blob&.byte_size
|
||||
end
|
||||
|
||||
def extract_image_quality
|
||||
return unless ['image/jpeg', 'image/pjpeg'].include? file_content_type
|
||||
|
||||
|
@ -212,13 +241,8 @@ class Asset < ApplicationRecord
|
|||
Rails.logger.info "There was an error extracting image quality - #{e}"
|
||||
end
|
||||
|
||||
def previewable?
|
||||
file.previewable_image? || file.previewable_document?
|
||||
end
|
||||
|
||||
def is_image?
|
||||
%r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}} ===
|
||||
file.content_type
|
||||
def image?
|
||||
file.blob.content_type == %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}}
|
||||
end
|
||||
|
||||
def text?
|
||||
|
@ -515,6 +539,29 @@ class Asset < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def previewable_document?
|
||||
previewable = Constants::PREVIEWABLE_FILE_TYPES.include?(file.blob&.content_type)
|
||||
|
||||
filename = file.blob&.filename
|
||||
content_type = file.blob&.content_type
|
||||
|
||||
extensions = %w(.xlsx .docx .pptx .xls .doc .ppt)
|
||||
# Mimetype sometimes recognizes Office files as zip files
|
||||
# In this case we also check the extension of the given file
|
||||
# Otherwise the conversion should fail if the file is being something else
|
||||
previewable ||= (content_type == 'application/zip' && extensions.include?(File.extname(filename)))
|
||||
|
||||
# Mimetype also sometimes recognizes '.xls' and '.ppt' files as
|
||||
# application/x-ole-storage (https://github.com/minad/mimemagic/issues/50)
|
||||
previewable ||= (content_type == 'application/x-ole-storage' && %w(.xls .ppt).include?(File.extname(filename)))
|
||||
|
||||
previewable
|
||||
end
|
||||
|
||||
def previewable_image?
|
||||
file.blob&.content_type =~ %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}}
|
||||
end
|
||||
|
||||
def filter_paperclip_errors
|
||||
if errors.size > 1
|
||||
temp_errors = errors[:file]
|
||||
|
|
|
@ -245,7 +245,7 @@ class Protocol < ApplicationRecord
|
|||
dest.file = src.file
|
||||
dest.save!
|
||||
|
||||
if dest.is_image?
|
||||
if dest.image?
|
||||
dest.file.reprocess!(:large)
|
||||
dest.file.reprocess!(:medium)
|
||||
end
|
||||
|
@ -334,7 +334,7 @@ class Protocol < ApplicationRecord
|
|||
asset2.created_by = current_user
|
||||
asset2.team = dest.team
|
||||
asset2.last_modified_by = current_user
|
||||
asset2.file_processing = true if asset.is_image?
|
||||
asset2.file_processing = true if asset.image?
|
||||
asset2.save!
|
||||
|
||||
step2.assets << asset2
|
||||
|
|
|
@ -77,13 +77,13 @@ module RepositoryActions
|
|||
new_asset.created_by = old_asset.created_by
|
||||
new_asset.team = @team
|
||||
new_asset.last_modified_by = @user
|
||||
new_asset.file_processing = true if old_asset.is_image?
|
||||
new_asset.file_processing = true if old_asset.image?
|
||||
new_asset.file = old_asset.file
|
||||
new_asset.save
|
||||
|
||||
return unless new_asset.valid?
|
||||
|
||||
if new_asset.is_image?
|
||||
if new_asset.image?
|
||||
new_asset.file.reprocess!(:large)
|
||||
new_asset.file.reprocess!(:medium)
|
||||
end
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<% if result.is_asset %>
|
||||
<% if result.asset.is_image? %>
|
||||
<% if result.asset.image? %>
|
||||
<span class="fas fa-paperclip"></span>
|
||||
<% else %>
|
||||
<span class="fas fa-paperclip"></span>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<% result ||= @result %>
|
||||
<% asset = result.asset %>
|
||||
<% is_image = result.asset.is_image? %>
|
||||
<% is_image = result.asset.image? %>
|
||||
<% comments = result.result_comments %>
|
||||
<% timestamp = asset.created_at %>
|
||||
<% icon_class = 'fas ' + (is_image ? 'fa-image' : 'fa-file') %>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<% asset ||= @asset %>
|
||||
<% is_image = asset.is_image? %>
|
||||
<% is_image = asset.image? %>
|
||||
<% timestamp = asset.created_at %>
|
||||
<% icon_class = 'fas ' + (is_image ? 'fa-image' : 'fa-file') %>
|
||||
<div class="report-element report-step-attachment-element report-step-asset-element" data-ts="<%= timestamp.to_i %>" data-type="step_asset" data-id='{ "asset_id": <%= asset.id %> }' data-scroll-id="<%= asset.id %>" data-name="<%=t "projects.reports.elements.step_asset.sidebar_name", file: asset.file_file_name %>" data-icon-class="<%= icon_class %>">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<% @asset_results.each do |asset| %>
|
||||
<h5>
|
||||
<% if asset.is_image? %>
|
||||
<% if asset.image? %>
|
||||
<span class="fas fa-paperclip"></span>
|
||||
<% else %>
|
||||
<% if wopi_file?(asset) %>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<% elsif result.is_table %>
|
||||
<span class="fas fa-table"></span>
|
||||
<% else %>
|
||||
<% if result.asset.is_image? %>
|
||||
<% if result.asset.image? %>
|
||||
<span class="fas fa-paperclip"></span>
|
||||
<% else %>
|
||||
<span class="fas fa-paperclip"></span>
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
<%= link_to download_asset_path(asset),
|
||||
class: 'file-preview-link',
|
||||
id: "modal_link#{asset.id}",
|
||||
data: {
|
||||
id: true,
|
||||
no_turbolink: true,
|
||||
status: asset.file.processing? ? 'asset-loading' : 'asset-present',
|
||||
'present-url': file_present_asset_path(asset.id),
|
||||
'preview-url': asset_file_preview_path(asset)
|
||||
} do %>
|
||||
<%= render partial: "shared/asset_link_placeholder.html.erb",
|
||||
locals: { asset: asset, display_image_tag: display_image_tag } %>
|
||||
data: { no_turbolink: true, id: true, status: 'asset-present', 'preview-url': asset_file_preview_path(asset) } do %>
|
||||
<% if asset.image? && display_image_tag %>
|
||||
<%= image_tag asset.medium_preview %>
|
||||
<% end %>
|
||||
<% if display_image_tag %>
|
||||
<p>
|
||||
<%= truncate(asset.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %>
|
||||
</p>
|
||||
<% else %>
|
||||
<span>
|
||||
<%= truncate(asset.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<% end %>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div>
|
||||
<% if asset.file.previewable_document? %>
|
||||
<%= image_tag(asset.url(:large)) %>
|
||||
<%= image_tag(asset.large_preview) %>
|
||||
<% else %>
|
||||
<i class="fas fa-10x <%= file_fa_icon_class(asset) %>"></i>
|
||||
<% end %>
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
<% if asset.file.processing? && (asset.is_image? || asset.file.previewable_document?) %>
|
||||
<% asset_status = 'asset-loading' %>
|
||||
<% present_url = file_present_asset_path(asset.id) %>
|
||||
<% else %>
|
||||
<% asset_status = 'asset-present' %>
|
||||
<% present_url = '' %>
|
||||
<% end %>
|
||||
|
||||
<div class="pseudo-attachment-container" style="order: <%= assets_count - i %>">
|
||||
<%= link_to download_asset_path(asset),
|
||||
|
@ -12,8 +5,6 @@
|
|||
id: "modal_link#{asset.id}",
|
||||
data: { no_turbolink: true,
|
||||
id: true,
|
||||
status: asset_status,
|
||||
'present-url': present_url,
|
||||
'preview-url': asset_file_preview_path(asset),
|
||||
'order-atoz': order_atoz,
|
||||
'order-ztoa': order_ztoa,
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
<% image_preview = (asset.is_image? || asset.file.previewable_document?) ? asset.url(:medium) : nil %>
|
||||
|
||||
<div class="attachment-placeholder pull-left">
|
||||
<div class="attachment-thumbnail <%= image_preview ? '' : 'no-shadow' %>">
|
||||
<% if image_preview %>
|
||||
<%= image_tag image_preview %>
|
||||
<div class="attachment-thumbnail <%= asset.previewable? ? '' : 'no-shadow' %>">
|
||||
<% if asset.previewable? %>
|
||||
<%= image_tag asset.medium_preview %>
|
||||
<% else %>
|
||||
<i class="fas <%= file_fa_icon_class(asset) if asset.file_file_name %>"></i>
|
||||
<i class="fas <%= file_fa_icon_class(asset) if asset.file_name %>"></i>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="attachment-label"><%= truncate(asset.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></div>
|
||||
<div class="attachment-label">
|
||||
<%= truncate(asset.file_name, length: Constants::FILENAME_TRUNCATION_LENGTH) %>
|
||||
</div>
|
||||
<div class="spencer-bonnet-modif">
|
||||
<%= t('protocols.steps.attachments.modified_label') %> <%= l(asset.updated_at, format: :full_date) if asset.updated_at %> <br>
|
||||
<%= number_to_human_size(asset.file.size) %>
|
||||
<%= number_to_human_size(asset.file_size) %>
|
||||
</div>
|
||||
|
||||
<% if edit_page %>
|
||||
<div class="remove-icon pull-right">
|
||||
<% unless ff.object.file.exists? && ff.object.locked? %>
|
||||
<% unless ff.object.file.attached? && ff.object.locked? %>
|
||||
<%= ff.remove_nested_fields_link do %>
|
||||
<span class="fas fa-trash"></span>
|
||||
<% end %>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env ruby
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
||||
load Gem.bin_path('bundler', 'bundle')
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'pathname'
|
||||
require 'fileutils'
|
||||
include FileUtils
|
||||
|
||||
# path to your application root.
|
||||
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
|
||||
def system!(*args)
|
||||
system(*args) || abort("\n== Command #{args} failed ==")
|
||||
|
@ -21,7 +20,6 @@ chdir APP_ROOT do
|
|||
# Install JavaScript dependencies if using Yarn
|
||||
# system('bin/yarn')
|
||||
|
||||
|
||||
# puts "\n== Copying sample files =="
|
||||
# unless File.exist?('config/database.yml')
|
||||
# cp 'config/database.yml.sample', 'config/database.yml'
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'pathname'
|
||||
require 'fileutils'
|
||||
include FileUtils
|
||||
|
||||
# path to your application root.
|
||||
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
|
||||
def system!(*args)
|
||||
system(*args) || abort("\n== Command #{args} failed ==")
|
||||
|
@ -18,6 +17,9 @@ chdir APP_ROOT do
|
|||
system! 'gem install bundler --conservative'
|
||||
system('bundle check') || system!('bundle install')
|
||||
|
||||
# Install JavaScript dependencies if using Yarn
|
||||
# system('bin/yarn')
|
||||
|
||||
puts "\n== Updating database =="
|
||||
system! 'bin/rails db:migrate'
|
||||
|
||||
|
|
6
bin/yarn
6
bin/yarn
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env ruby
|
||||
VENDOR_PATH = File.expand_path('..', __dir__)
|
||||
Dir.chdir(VENDOR_PATH) do
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
begin
|
||||
exec "yarnpkg #{ARGV.join(" ")}"
|
||||
exec "yarnpkg", *ARGV
|
||||
rescue Errno::ENOENT
|
||||
$stderr.puts "Yarn executable was not detected in the system."
|
||||
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
||||
|
||||
require 'bundler/setup' # Set up gems listed in the Gemfile.
|
||||
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
|
||||
|
|
|
@ -6,5 +6,5 @@ test:
|
|||
|
||||
production:
|
||||
adapter: redis
|
||||
url: redis://localhost:6379/1
|
||||
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
|
||||
channel_prefix: scinote_production
|
||||
|
|
|
@ -27,8 +27,6 @@ Rails.application.configure do
|
|||
from: Rails.application.secrets.mailer_from,
|
||||
reply_to: Rails.application.secrets.mailer_reply_to
|
||||
}
|
||||
config.action_mailer.raise_delivery_errors = false
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
if ENV['CUCUMBER'] == 'cucumber'
|
||||
config.action_mailer.delivery_method = :test
|
||||
|
@ -57,6 +55,14 @@ Rails.application.configure do
|
|||
password: Rails.application.secrets.mailer_password
|
||||
}
|
||||
|
||||
# Store uploaded files on the local file system (see config/storage.yml for options)
|
||||
config.active_storage.service = ENV['ACTIVESTORAGE_SERVICE'] || :local
|
||||
|
||||
# Don't care if the mailer can't send.
|
||||
config.action_mailer.raise_delivery_errors = false
|
||||
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
# Print deprecation notices to the Rails logger.
|
||||
config.active_support.deprecation = :log
|
||||
|
||||
|
@ -65,6 +71,9 @@ Rails.application.configure do
|
|||
# Raise an error on page load if there are pending migrations.
|
||||
config.active_record.migration_error = :page_load
|
||||
|
||||
# Highlight code that triggered database queries in logs.
|
||||
config.active_record.verbose_query_logs = true
|
||||
|
||||
# Debug mode disables concatenation and preprocessing of assets.
|
||||
# This option may cause significant delays in view rendering with a large
|
||||
# number of complex assets.
|
||||
|
@ -86,6 +95,9 @@ Rails.application.configure do
|
|||
# one you use and put it into application.yml!
|
||||
BetterErrors::Middleware.allow_ip! ENV['TRUSTED_IP'] if ENV['TRUSTED_IP']
|
||||
|
||||
# Suppress logger output for asset requests.
|
||||
config.assets.quiet = false
|
||||
|
||||
# Raises error for missing translations
|
||||
config.action_view.raise_on_missing_translations = true
|
||||
|
||||
|
@ -108,11 +120,7 @@ Rails.application.configure do
|
|||
|
||||
# Use an evented file watcher to asynchronously detect changes in source code,
|
||||
# routes, locales, etc. This feature depends on the listen gem.
|
||||
#config.file_watcher = ActiveSupport::EventedFileUpdateChecker
|
||||
|
||||
# Suppress logger output for asset requests. By default logger output is
|
||||
# enabled.
|
||||
# config.assets.quiet = true
|
||||
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
|
||||
|
||||
# Enable/disable caching. By default caching is disabled.
|
||||
if Rails.root.join('tmp/caching-dev.txt').exist?
|
||||
|
|
|
@ -42,44 +42,44 @@ Rails.application.configure do
|
|||
password: Rails.application.secrets.mailer_password
|
||||
}
|
||||
#config.action_mailer.perform_deliveries = false
|
||||
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
|
||||
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
|
||||
# config.require_master_key = true
|
||||
|
||||
# Enable Rack::Cache to put a simple HTTP cache in front of your application
|
||||
# Add `rack-cache` to your Gemfile before enabling this.
|
||||
# For large-scale production use, consider using a caching reverse proxy like
|
||||
# NGINX, varnish or squid.
|
||||
# config.action_dispatch.rack_cache = true
|
||||
|
||||
# Compress JavaScripts and CSS.
|
||||
config.assets.js_compressor = Uglifier.new(harmony: true)
|
||||
config.assets.js_compressor = :uglifier
|
||||
# config.assets.css_compressor = :sass
|
||||
|
||||
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
||||
config.assets.compile = false
|
||||
|
||||
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
|
||||
# yet still be able to expire them through the digest params.
|
||||
config.assets.digest = true
|
||||
|
||||
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
|
||||
|
||||
# Specifies the header that your server uses for sending files.
|
||||
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
|
||||
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
|
||||
|
||||
# Store uploaded files on the local file system (see config/storage.yml for options)
|
||||
config.active_storage.service = ENV['ACTIVESTORAGE_SERVICE'] || :local
|
||||
|
||||
# Mount Action Cable outside main process or domain
|
||||
# config.action_cable.mount_path = nil
|
||||
# config.action_cable.url = 'wss://example.com/cable'
|
||||
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
|
||||
|
||||
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
|
||||
config.force_ssl = ENV['RAILS_FORCE_SSL'].present?
|
||||
|
||||
# Display info and higher on production.
|
||||
config.log_level = :info
|
||||
# Use the lowest log level to ensure availability of diagnostic information
|
||||
# when problems arise.
|
||||
config.log_level = :debug
|
||||
|
||||
# Prepend all log lines with the following tags.
|
||||
# config.log_tags = [ :subdomain, :uuid ]
|
||||
config.log_tags = [ :request_id ]
|
||||
|
||||
|
||||
# Use a different logger for distributed setups.
|
||||
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
|
||||
|
||||
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
|
||||
# config.action_controller.asset_host = 'http://assets.example.com'
|
||||
|
||||
# Ignore bad email addresses and do not raise email delivery errors.
|
||||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||
|
@ -87,7 +87,7 @@ Rails.application.configure do
|
|||
|
||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||
# the I18n.default_locale when a translation cannot be found).
|
||||
config.i18n.fallbacks = [I18n.default_locale]
|
||||
config.i18n.fallbacks = true
|
||||
|
||||
# Send deprecation notices to registered listeners.
|
||||
config.active_support.deprecation = :notify
|
||||
|
@ -119,7 +119,7 @@ Rails.application.configure do
|
|||
# require 'syslog/logger'
|
||||
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
|
||||
|
||||
if ENV['RAILS_LOG_TO_STDOUT'].present?
|
||||
if ENV["RAILS_LOG_TO_STDOUT"].present?
|
||||
logger = ActiveSupport::Logger.new(STDOUT)
|
||||
logger.formatter = config.log_formatter
|
||||
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
||||
|
@ -132,30 +132,12 @@ Rails.application.configure do
|
|||
config.active_job.queue_adapter = :delayed_job
|
||||
config.active_job.queue_name_prefix = "scinote_#{Rails.env}"
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
|
||||
|
||||
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
|
||||
# config.action_controller.asset_host = 'http://assets.example.com'
|
||||
|
||||
# Specifies the header that your server uses for sending files.
|
||||
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
|
||||
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
|
||||
|
||||
# Mount Action Cable outside main process or domain
|
||||
# config.action_cable.mount_path = nil
|
||||
# config.action_cable.url = 'wss://example.com/cable'
|
||||
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
|
||||
|
||||
# Disable serving static files from the `/public` folder by default since
|
||||
# Apache or NGINX already handles this.
|
||||
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
|
||||
|
||||
# Attempt to read encrypted secrets from `config/secrets.yml.enc`.
|
||||
# Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
|
||||
# `config/secrets.yml.key`.
|
||||
config.read_encrypted_secrets = true
|
||||
|
||||
# Enable new team on sign up
|
||||
new_team_on_signup = ENV['NEW_TEAM_ON_SIGNUP'] || 'true'
|
||||
if new_team_on_signup == 'true'
|
||||
|
|
|
@ -43,6 +43,11 @@ Rails.application.configure do
|
|||
# Disable request forgery protection in test environment.
|
||||
config.action_controller.allow_forgery_protection = false
|
||||
|
||||
# Store uploaded files on the local file system in a temporary directory
|
||||
config.active_storage.service = :test
|
||||
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
# Tell Action Mailer not to deliver emails to the real world.
|
||||
# The :test delivery method accumulates sent emails in the
|
||||
# ActionMailer::Base.deliveries array.
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# ApplicationController.renderer.defaults.merge!(
|
||||
# http_host: 'example.org',
|
||||
# https: false
|
||||
# )
|
||||
# ActiveSupport::Reloader.to_prepare do
|
||||
# ApplicationController.renderer.defaults.merge!(
|
||||
# http_host: 'example.org',
|
||||
# https: false
|
||||
# )
|
||||
# end
|
||||
|
|
25
config/initializers/content_security_policy.rb
Normal file
25
config/initializers/content_security_policy.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Define an application-wide content security policy
|
||||
# For further information see the following documentation
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
||||
|
||||
# Rails.application.config.content_security_policy do |policy|
|
||||
# policy.default_src :self, :https
|
||||
# policy.font_src :self, :https, :data
|
||||
# policy.img_src :self, :https, :data
|
||||
# policy.object_src :none
|
||||
# policy.script_src :self, :https
|
||||
# policy.style_src :self, :https
|
||||
|
||||
# # Specify URI for violation reports
|
||||
# # policy.report_uri "/csp-violation-report-endpoint"
|
||||
# end
|
||||
|
||||
# If you are using UJS then enable automatic nonce generation
|
||||
# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
|
||||
|
||||
# Report CSP violations to a specified URI
|
||||
# For further information see the following documentation:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
||||
# Rails.application.config.content_security_policy_report_only = true
|
38
config/initializers/new_framework_defaults_5_2.rb
Normal file
38
config/initializers/new_framework_defaults_5_2.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
#
|
||||
# This file contains migration options to ease your Rails 5.2 upgrade.
|
||||
#
|
||||
# Once upgraded flip defaults one by one to migrate to the new default.
|
||||
#
|
||||
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
|
||||
|
||||
# Make Active Record use stable #cache_key alongside new #cache_version method.
|
||||
# This is needed for recyclable cache keys.
|
||||
# Rails.application.config.active_record.cache_versioning = true
|
||||
|
||||
# Use AES-256-GCM authenticated encryption for encrypted cookies.
|
||||
# Also, embed cookie expiry in signed or encrypted cookies for increased security.
|
||||
#
|
||||
# This option is not backwards compatible with earlier Rails versions.
|
||||
# It's best enabled when your entire app is migrated and stable on 5.2.
|
||||
#
|
||||
# Existing cookies will be converted on read then written with the new scheme.
|
||||
# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true
|
||||
|
||||
# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages
|
||||
# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true.
|
||||
# Rails.application.config.active_support.use_authenticated_message_encryption = true
|
||||
|
||||
# Add default protection from forgery to ActionController::Base instead of in
|
||||
# ApplicationController.
|
||||
# Rails.application.config.action_controller.default_protect_from_forgery = true
|
||||
|
||||
# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and
|
||||
# 'f' after migrating old data.
|
||||
# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
|
||||
|
||||
# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header.
|
||||
# Rails.application.config.active_support.use_sha1_digests = true
|
||||
|
||||
# Make `form_with` generate id attributes for any generated HTML tags.
|
||||
# Rails.application.config.action_view.form_with_generates_ids = true
|
|
@ -61,27 +61,6 @@ Paperclip::Attachment.class_eval do
|
|||
def fetch
|
||||
Paperclip.io_adapters.for self
|
||||
end
|
||||
|
||||
def previewable_document?
|
||||
previewable = Constants::PREVIEWABLE_FILE_TYPES.include?(content_type)
|
||||
|
||||
extensions = %w(.xlsx .docx .pptx .xls .doc .ppt)
|
||||
# Mimetype sometimes recognizes Office files as zip files
|
||||
# In this case we also check the extension of the given file
|
||||
# Otherwise the conversion should fail if the file is being something else
|
||||
previewable ||= (content_type == 'application/zip' && extensions.include?(File.extname(original_filename)))
|
||||
|
||||
# Mimetype also sometimes recognizes '.xls' and '.ppt' files as
|
||||
# application/x-ole-storage (https://github.com/minad/mimemagic/issues/50)
|
||||
previewable ||=
|
||||
(content_type == 'application/x-ole-storage' && %w(.xls .ppt).include?(File.extname(original_filename)))
|
||||
|
||||
previewable
|
||||
end
|
||||
|
||||
def previewable_image?
|
||||
content_type =~ %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}}
|
||||
end
|
||||
end
|
||||
|
||||
module Paperclip
|
||||
|
|
34
config/storage.yml
Normal file
34
config/storage.yml
Normal file
|
@ -0,0 +1,34 @@
|
|||
test:
|
||||
service: Disk
|
||||
root: <%= Rails.root.join("tmp/storage") %>
|
||||
|
||||
local:
|
||||
service: Disk
|
||||
root: <%= Rails.root.join("storage") %>
|
||||
|
||||
amazon:
|
||||
service: CustomS3
|
||||
access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
|
||||
secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>
|
||||
region: <%= ENV["AWS_REGION"] %>
|
||||
bucket: <%= ENV["S3_BUCKET"] %>
|
||||
subfolder: <%= ENV["S3_SUBFOLDER"] %>
|
||||
|
||||
# Remember not to checkin your GCS keyfile to a repository
|
||||
# google:
|
||||
# service: GCS
|
||||
# project: your_project
|
||||
# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
|
||||
# bucket: your_own_bucket
|
||||
|
||||
# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
|
||||
# microsoft:
|
||||
# service: AzureStorage
|
||||
# storage_account_name: your_account_name
|
||||
# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
|
||||
# container: your_container_name
|
||||
|
||||
# mirror:
|
||||
# service: Mirror
|
||||
# primary: local
|
||||
# mirrors: [ amazon, google, microsoft ]
|
|
@ -0,0 +1,27 @@
|
|||
# This migration comes from active_storage (originally 20170806125915)
|
||||
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :active_storage_blobs do |t|
|
||||
t.string :key, null: false
|
||||
t.string :filename, null: false
|
||||
t.string :content_type
|
||||
t.text :metadata
|
||||
t.bigint :byte_size, null: false
|
||||
t.string :checksum, null: false
|
||||
t.datetime :created_at, null: false
|
||||
|
||||
t.index [ :key ], unique: true
|
||||
end
|
||||
|
||||
create_table :active_storage_attachments do |t|
|
||||
t.string :name, null: false
|
||||
t.references :record, null: false, polymorphic: true, index: false
|
||||
t.references :blob, null: false
|
||||
|
||||
t.datetime :created_at, null: false
|
||||
|
||||
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||
end
|
||||
end
|
||||
end
|
117
db/migrate/20190613134100_convert_to_active_storage.rb
Normal file
117
db/migrate/20190613134100_convert_to_active_storage.rb
Normal file
|
@ -0,0 +1,117 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ConvertToActiveStorage < ActiveRecord::Migration[5.2]
|
||||
require 'open-uri'
|
||||
|
||||
ID_PARTITION_LIMIT = 1_000_000_000
|
||||
DIGEST = OpenSSL::Digest.const_get('SHA1').new
|
||||
|
||||
def up
|
||||
ActiveRecord::Base.connection.raw_connection.prepare('active_storage_blob_statement', <<-SQL)
|
||||
INSERT INTO active_storage_blobs (
|
||||
key, filename, content_type, metadata, byte_size, checksum, created_at
|
||||
) VALUES ($1, $2, $3, '{}', $4, $5, $6)
|
||||
RETURNING id;
|
||||
SQL
|
||||
|
||||
ActiveRecord::Base.connection.raw_connection.prepare('active_storage_attachment_statement', <<-SQL)
|
||||
INSERT INTO active_storage_attachments (
|
||||
name, record_type, record_id, blob_id, created_at
|
||||
) VALUES ($1, $2, $3, $4, $5)
|
||||
SQL
|
||||
|
||||
Rails.application.eager_load!
|
||||
models = ApplicationRecord.descendants.reject(&:abstract_class?)
|
||||
|
||||
transaction do
|
||||
models.each do |model|
|
||||
attachments = model.column_names.map do |c|
|
||||
$1 if c =~ /(.+)_file_name$/
|
||||
end.compact
|
||||
|
||||
next if attachments.blank?
|
||||
|
||||
model.find_each.each do |instance|
|
||||
attachments.each do |attachment|
|
||||
next if instance.__send__("#{attachment}_file_name").blank?
|
||||
|
||||
res = ActiveRecord::Base.connection.raw_connection.exec_prepared(
|
||||
'active_storage_blob_statement', [
|
||||
key(instance, attachment),
|
||||
instance.__send__("#{attachment}_file_name"),
|
||||
instance.__send__("#{attachment}_content_type"),
|
||||
instance.__send__("#{attachment}_file_size") || 0,
|
||||
checksum(attachment),
|
||||
instance.updated_at.iso8601
|
||||
]
|
||||
)
|
||||
|
||||
ActiveRecord::Base.connection.raw_connection.exec_prepared(
|
||||
'active_storage_attachment_statement', [
|
||||
attachment,
|
||||
model.name,
|
||||
instance.id,
|
||||
res[0]['id'],
|
||||
instance.updated_at.iso8601
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
# raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
ID_PARTITION_LIMIT = 1_000_000_000
|
||||
DIGEST = OpenSSL::Digest.const_get('SHA1').new
|
||||
|
||||
def id_partition(id)
|
||||
if id < ID_PARTITION_LIMIT
|
||||
format('%09d', id).scan(/\d{3}/).join('/')
|
||||
else
|
||||
format('%012d', id).scan(/\d{3}/).join('/')
|
||||
end
|
||||
end
|
||||
|
||||
def hash_data(instance, attachment)
|
||||
"#{instance.class.to_s.underscore.pluralize}/#{attachment.pluralize}/#{instance.id}/original"
|
||||
end
|
||||
|
||||
def interpolate(pattern, instance, attachment)
|
||||
path = pattern
|
||||
path = path.gsub(':class', instance.class.to_s.underscore.pluralize)
|
||||
path = path.gsub(':attachment', attachment.pluralize)
|
||||
path = path.gsub(':id_partition', id_partition(instance.id))
|
||||
path = path.gsub(':hash', OpenSSL::HMAC.hexdigest(DIGEST,
|
||||
ENV['PAPERCLIP_HASH_SECRET'],
|
||||
hash_data(instance, attachment)))
|
||||
path.gsub(':filename', instance.__send__("#{attachment}_file_name"))
|
||||
end
|
||||
|
||||
def key(instance, attachment)
|
||||
# SecureRandom.uuid
|
||||
# Alternatively:
|
||||
if ENV['PAPERCLIP_STORAGE'] == 's3'
|
||||
interpolate(':class/:attachment/:id_partition/:hash/original/:filename', instance, attachment)
|
||||
else
|
||||
key = SecureRandom.uuid
|
||||
File.join('storage', key.first(2), key.first(4).last(2))
|
||||
end
|
||||
end
|
||||
|
||||
def checksum(_attachment)
|
||||
'dummy'
|
||||
# local files stored on disk:
|
||||
# url = attachment.path
|
||||
# Digest::MD5.base64digest(File.read(url))
|
||||
|
||||
# remote files stored on another person's computer:
|
||||
# url = attachment.url
|
||||
# Digest::MD5.base64digest(Net::HTTP.get(URI(url)))
|
||||
end
|
||||
end
|
28
db/schema.rb
28
db/schema.rb
|
@ -10,12 +10,33 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20190520135317) do
|
||||
ActiveRecord::Schema.define(version: 2019_06_13_134100) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
enable_extension "pg_trgm"
|
||||
enable_extension "btree_gist"
|
||||
enable_extension "pg_trgm"
|
||||
enable_extension "plpgsql"
|
||||
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.bigint "blob_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
||||
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_blobs", force: :cascade do |t|
|
||||
t.string "key", null: false
|
||||
t.string "filename", null: false
|
||||
t.string "content_type"
|
||||
t.text "metadata"
|
||||
t.bigint "byte_size", null: false
|
||||
t.string "checksum", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
||||
end
|
||||
|
||||
create_table "activities", force: :cascade do |t|
|
||||
t.bigint "my_module_id"
|
||||
|
@ -941,6 +962,7 @@ ActiveRecord::Schema.define(version: 20190520135317) do
|
|||
t.index ["user_id"], name: "index_zip_exports_on_user_id"
|
||||
end
|
||||
|
||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "activities", "experiments"
|
||||
add_foreign_key "activities", "my_modules"
|
||||
add_foreign_key "activities", "projects"
|
||||
|
|
|
@ -44,6 +44,7 @@ services:
|
|||
bash -c "./bin/webpack-dev-server"
|
||||
environment:
|
||||
- RAILS_ENV=development
|
||||
- WORKER=true
|
||||
volumes:
|
||||
- .:/usr/src/app
|
||||
- scinote_development_bundler:/usr/local/bundle/
|
||||
|
|
172
lib/active_storage/service/custom_s3_service.rb
Normal file
172
lib/active_storage/service/custom_s3_service.rb
Normal file
|
@ -0,0 +1,172 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Copyright (c) 2017-2019 David Heinemeier Hansson, Basecamp
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
gem 'aws-sdk-s3', '~> 1.14'
|
||||
|
||||
require 'aws-sdk-s3'
|
||||
require 'active_support/core_ext/numeric/bytes'
|
||||
|
||||
module ActiveStorage
|
||||
# Wraps the Amazon Simple Storage Service (S3) as an Active Storage service.
|
||||
# See ActiveStorage::Service for the generic API documentation that applies to all services.
|
||||
class Service::CustomS3Service < Service
|
||||
attr_reader :client, :bucket
|
||||
attr_reader :multipart_upload_threshold, :upload_options
|
||||
attr_reader :subfolder
|
||||
|
||||
def initialize(bucket:, upload: {}, **options)
|
||||
@subfolder = options.delete(:subfolder)
|
||||
|
||||
@client = Aws::S3::Resource.new(**options)
|
||||
@bucket = @client.bucket(bucket)
|
||||
|
||||
@multipart_upload_threshold = upload.fetch(:multipart_threshold, 100.megabytes)
|
||||
@upload_options = upload
|
||||
end
|
||||
|
||||
def upload(key, io, checksum: nil, content_type: nil, **)
|
||||
instrument :upload, key: key, checksum: checksum do
|
||||
if io.size < multipart_upload_threshold
|
||||
upload_with_single_part key, io, checksum: checksum, content_type: content_type
|
||||
else
|
||||
upload_with_multipart key, io, content_type: content_type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def download(key, &block)
|
||||
if block_given?
|
||||
instrument :streaming_download, key: key do
|
||||
stream(key, &block)
|
||||
end
|
||||
else
|
||||
instrument :download, key: key do
|
||||
object_for(key).get.body.string.force_encoding(Encoding::BINARY)
|
||||
rescue Aws::S3::Errors::NoSuchKey
|
||||
raise ActiveStorage::FileNotFoundError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def download_chunk(key, range)
|
||||
instrument :download_chunk, key: key, range: range do
|
||||
object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}")
|
||||
.body
|
||||
.string
|
||||
.force_encoding(Encoding::BINARY)
|
||||
rescue Aws::S3::Errors::NoSuchKey
|
||||
raise ActiveStorage::FileNotFoundError
|
||||
end
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
instrument :delete, key: key do
|
||||
object_for(key).delete
|
||||
end
|
||||
end
|
||||
|
||||
def delete_prefixed(prefix)
|
||||
instrument :delete_prefixed, prefix: prefix do
|
||||
prefix = subfolder.present? ? File.join(subfolder, prefix) : prefix
|
||||
bucket.objects(prefix: prefix).batch_delete!
|
||||
end
|
||||
end
|
||||
|
||||
def exist?(key)
|
||||
instrument :exist, key: key do |payload|
|
||||
answer = object_for(key).exists?
|
||||
payload[:exist] = answer
|
||||
answer
|
||||
end
|
||||
end
|
||||
|
||||
def url(key, expires_in:, filename:, disposition:, content_type:)
|
||||
instrument :url, key: key do |payload|
|
||||
generated_url = object_for(key).presigned_url :get, expires_in: expires_in.to_i,
|
||||
response_content_disposition: content_disposition_with(type: disposition, filename: filename),
|
||||
response_content_type: content_type
|
||||
|
||||
payload[:url] = generated_url
|
||||
|
||||
generated_url
|
||||
end
|
||||
end
|
||||
|
||||
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
|
||||
instrument :url, key: key do |payload|
|
||||
generated_url = object_for(key).presigned_url :put, expires_in: expires_in.to_i,
|
||||
content_type: content_type, content_length: content_length, content_md5: checksum
|
||||
|
||||
payload[:url] = generated_url
|
||||
|
||||
generated_url
|
||||
end
|
||||
end
|
||||
|
||||
def headers_for_direct_upload(_key, content_type:, checksum:, **)
|
||||
{ 'Content-Type' => content_type, 'Content-MD5' => checksum }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
MAXIMUM_UPLOAD_PARTS_COUNT = 10000
|
||||
MINIMUM_UPLOAD_PART_SIZE = 5.megabytes
|
||||
|
||||
def upload_with_single_part(key, io, checksum: nil, content_type: nil)
|
||||
object_for(key).put(body: io, content_md5: checksum, content_type: content_type, **upload_options)
|
||||
rescue Aws::S3::Errors::BadDigest
|
||||
raise ActiveStorage::IntegrityError
|
||||
end
|
||||
|
||||
def upload_with_multipart(key, io, content_type: nil)
|
||||
part_size = [io.size.fdiv(MAXIMUM_UPLOAD_PARTS_COUNT).ceil, MINIMUM_UPLOAD_PART_SIZE].max
|
||||
|
||||
object_for(key).upload_stream(content_type: content_type, part_size: part_size, **upload_options) do |out|
|
||||
IO.copy_stream(io, out)
|
||||
end
|
||||
end
|
||||
|
||||
def object_for(key)
|
||||
key = subfolder.present? ? File.join(subfolder, key) : key
|
||||
bucket.object(key)
|
||||
end
|
||||
|
||||
# Reads the object for the given key in chunks, yielding each to the block.
|
||||
def stream(key)
|
||||
object = object_for(key)
|
||||
|
||||
chunk_size = 5.megabytes
|
||||
offset = 0
|
||||
|
||||
raise ActiveStorage::FileNotFoundError unless object.exists?
|
||||
|
||||
while offset < object.content_length
|
||||
yield object.get(range: "bytes=#{offset}-#{offset + chunk_size - 1}")
|
||||
.body
|
||||
.string
|
||||
.force_encoding(Encoding::BINARY)
|
||||
offset += chunk_size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
69
lib/tasks/active_storage.rake
Normal file
69
lib/tasks/active_storage.rake
Normal file
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
namespace :active_storage do
|
||||
ID_PARTITION_LIMIT = 1_000_000_000
|
||||
DIGEST = OpenSSL::Digest.const_get('SHA1').new
|
||||
|
||||
def id_partition(id)
|
||||
if id < ID_PARTITION_LIMIT
|
||||
format('%09d', id).scan(/\d{3}/).join('/')
|
||||
else
|
||||
format('%012d', id).scan(/\d{3}/).join('/')
|
||||
end
|
||||
end
|
||||
|
||||
def hash_data(attachment)
|
||||
"#{attachment.record_type.underscore.pluralize}/#{attachment.name.pluralize}/#{attachment.record.id}/original"
|
||||
end
|
||||
|
||||
def interpolate(pattern, attachment)
|
||||
path = pattern
|
||||
path = path.gsub(':class', attachment.record_type.underscore.pluralize)
|
||||
path = path.gsub(':attachment', attachment.name.pluralize)
|
||||
path = path.gsub(':id_partition', id_partition(attachment.record.id))
|
||||
path = path.gsub(':hash', OpenSSL::HMAC.hexdigest(DIGEST, ENV['PAPERCLIP_HASH_SECRET'], hash_data(attachment)))
|
||||
path.gsub(':filename', attachment.blob.filename.to_s)
|
||||
end
|
||||
|
||||
desc 'Copy all files from Paperclip to ActiveStorage'
|
||||
task :migrate_files, [:before] => :environment do |_, _args|
|
||||
if ENV['PAPERCLIP_STORAGE'] == 'filesystem'
|
||||
local_path = "#{Rails.root}/public/system/:class/:attachment/:id_partition/:hash/original/:filename"
|
||||
|
||||
ActiveStorage::Attachment.find_each do |attachment|
|
||||
src = interpolate(local_path, attachment)
|
||||
dst_dir = File.join(
|
||||
'storage',
|
||||
attachment.blob.key.first(2),
|
||||
attachment.blob.key.first(4).last(2)
|
||||
)
|
||||
dst = File.join(dst_dir, attachment.blob.key)
|
||||
|
||||
FileUtils.mkdir_p(dst_dir)
|
||||
puts "Copying #{src} to #{dst}"
|
||||
FileUtils.cp(src, dst)
|
||||
end
|
||||
elsif ENV['PAPERCLIP_STORAGE'] == 's3'
|
||||
|
||||
s3_path = ':class/:attachment/:id_partition/:hash/original/:filename'
|
||||
s3_path = "#{ENV['S3_SUBFOLDER']}/" + s3_path if ENV['S3_SUBFOLDER']
|
||||
|
||||
ActiveStorage::Attachment.find_each do |attachment|
|
||||
src_path = interpolate(s3_path, attachment)
|
||||
|
||||
next unless S3_BUCKET.object(src_path).exists?
|
||||
|
||||
dst_path = ENV['S3_SUBFOLDER'] ? File.join(ENV['S3_SUBFOLDER'], attachment.blob.key) : attachment.blob.key
|
||||
|
||||
puts "Copying #{src_path} to #{dst_path}"
|
||||
|
||||
s3.copy_object(bucket: S3_BUCKET.name,
|
||||
copy_source: S3_BUCKET.name + src_path,
|
||||
key: dst_path)
|
||||
rescue StandardError => e
|
||||
puts 'Caught exception copying object ' + src_path + ':'
|
||||
puts e.message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -36,7 +36,7 @@ namespace :paperclip do
|
|||
assets = assets.where('updated_at < ?', eval(args[:before]))
|
||||
end
|
||||
assets.find_each(batch_size: 100) do |asset|
|
||||
next unless asset.is_image?
|
||||
next unless asset.image?
|
||||
begin
|
||||
asset.file.reprocess! :medium, :large
|
||||
rescue
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
FactoryBot.define do
|
||||
factory :activity do
|
||||
type_of :create_project
|
||||
message Faker::Lorem.sentence(10)
|
||||
type_of { :create_project }
|
||||
message { Faker::Lorem.sentence(10) }
|
||||
subject { create :project }
|
||||
owner { create :user }
|
||||
team
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
FactoryBot.define do
|
||||
factory :asset_text_datum do
|
||||
data "Sample name\tSample type\n" + "sample6\tsample\n" + "\n"
|
||||
data { "Sample name\tSample type\n" + "sample6\tsample\n" + "\n" }
|
||||
asset
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
FactoryBot.define do
|
||||
factory :asset do
|
||||
file_file_name 'sample_file.txt'
|
||||
file_content_type 'text/plain'
|
||||
file_file_size 69
|
||||
version 1
|
||||
estimated_size 232
|
||||
file_processing false
|
||||
file_file_name { 'sample_file.txt' }
|
||||
file_content_type { 'text/plain' }
|
||||
file_file_size { 69 }
|
||||
version { 1 }
|
||||
estimated_size { 232 }
|
||||
file_processing { false }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
FactoryBot.define do
|
||||
factory :custom_field do
|
||||
name 'My custom field'
|
||||
name { 'My custom field' }
|
||||
user
|
||||
team
|
||||
end
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
FactoryBot.define do
|
||||
factory :notification do
|
||||
title '<i>Admin</i> was added as Owner to project ' \
|
||||
'<strong>Demo project - qPCR</strong> by <i>User</i>.'
|
||||
message 'Project: <a href=\"/projects/3\"> Demo project - qPCR</a>'
|
||||
type_of 'assignment'
|
||||
title { '<i>Admin</i> was added as Owner to project ' \
|
||||
'<strong>Demo project - qPCR</strong> by <i>User</i>.' }
|
||||
message { 'Project: <a href=\"/projects/3\"> Demo project - qPCR</a>' }
|
||||
type_of { 'assignment' }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,22 +5,22 @@ FactoryBot.define do
|
|||
sequence(:name) { |n| "My column-#{n}" }
|
||||
created_by { create :user }
|
||||
repository
|
||||
data_type :RepositoryTextValue
|
||||
data_type { :RepositoryTextValue }
|
||||
|
||||
trait :text_type do
|
||||
data_type :RepositoryTextValue
|
||||
data_type { :RepositoryTextValue }
|
||||
end
|
||||
|
||||
trait :date_type do
|
||||
data_type :RepositoryDateValue
|
||||
data_type { :RepositoryDateValue }
|
||||
end
|
||||
|
||||
trait :list_type do
|
||||
data_type :RepositoryListValue
|
||||
data_type { :RepositoryListValue }
|
||||
end
|
||||
|
||||
trait :asset_type do
|
||||
data_type :RepositoryAssetValue
|
||||
data_type { :RepositoryAssetValue }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
FactoryBot.define do
|
||||
factory :sample_group do
|
||||
name { Faker::Name.unique.name }
|
||||
color Faker::Color.hex_color
|
||||
color { Faker::Color.hex_color }
|
||||
team
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,8 @@ FactoryBot.define do
|
|||
sequence(:name) { |n| "My team-#{n}" }
|
||||
description { Faker::Lorem.sentence }
|
||||
space_taken { 1048576 }
|
||||
without_templates true
|
||||
without_intro_demo true
|
||||
without_templates { true }
|
||||
without_intro_demo { true }
|
||||
trait :with_members do
|
||||
users { create_list :user, 3 }
|
||||
end
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
FactoryBot.define do
|
||||
factory :tiny_mce_asset do
|
||||
association :team, factory: :team
|
||||
image_file_name 'sample_file.jpg'
|
||||
image_content_type 'image/jpeg'
|
||||
image_file_size 69
|
||||
image_file_name { 'sample_file.jpg' }
|
||||
image_content_type { 'image/jpeg' }
|
||||
image_file_size { 69 }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
FactoryBot.define do
|
||||
factory :user_identity do
|
||||
user
|
||||
uid Faker::Crypto.unique.sha1
|
||||
provider Faker::App.unique.name
|
||||
uid { Faker::Crypto.unique.sha1 }
|
||||
provider { Faker::App.unique.name }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
FactoryBot.define do
|
||||
factory :user_notification do
|
||||
checked false
|
||||
checked { false }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,13 +5,13 @@ FactoryBot.define do
|
|||
user
|
||||
team
|
||||
trait :admin do
|
||||
role 'admin'
|
||||
role { 'admin' }
|
||||
end
|
||||
trait :guest do
|
||||
role 'guest'
|
||||
role { 'guest' }
|
||||
end
|
||||
trait :normal_user do # default enum by DB
|
||||
role 'normal_user'
|
||||
role { 'normal_user' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
FactoryBot.define do
|
||||
factory :user do
|
||||
full_name 'admin'
|
||||
initials 'AD'
|
||||
full_name { 'admin' }
|
||||
initials { 'AD' }
|
||||
sequence(:email) { |n| "user-#{n}@example.com" }
|
||||
password 'asdf1243'
|
||||
password_confirmation 'asdf1243'
|
||||
current_sign_in_at DateTime.now
|
||||
password { 'asdf1243' }
|
||||
password_confirmation { 'asdf1243' }
|
||||
current_sign_in_at { DateTime.now }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
FactoryBot.define do
|
||||
factory :wopi_app do
|
||||
sequence(:name) { |n| "WopiApp-#{n}" }
|
||||
icon 'app-icon'
|
||||
icon { 'app-icon' }
|
||||
wopi_discovery
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue