Merge branch 'okriuchykhin-ok_SCI_3539' into features/marvinjs-integration

This commit is contained in:
Anton Ignatov 2019-07-01 10:44:29 +02:00
commit c9ae9f056e
54 changed files with 859 additions and 308 deletions

3
.gitignore vendored
View file

@ -35,6 +35,9 @@ ehthumbs.db
# Ignore temporary files
public/system/*
# Ignore ActiveStorage Disc service storage directory
storage/
# Ignore robots.txt
public/robots.txt

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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',

View file

@ -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

View file

@ -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

View file

@ -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]

View 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

View file

@ -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

View file

@ -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>

View file

@ -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') %>

View 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 %>">

View file

@ -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) %>

View file

@ -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>

View file

@ -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 %>

View file

@ -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 %>

View file

@ -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,

View file

@ -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 %>

View file

@ -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')

View file

@ -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'

View file

@ -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'

View file

@ -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"

View file

@ -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.

View file

@ -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

View file

@ -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?

View file

@ -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'

View file

@ -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.

View file

@ -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

View 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

View 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

View file

@ -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
View 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 ]

View file

@ -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

View 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

View file

@ -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"

View file

@ -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/

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -2,7 +2,7 @@
FactoryBot.define do
factory :custom_field do
name 'My custom field'
name { 'My custom field' }
user
team
end

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -2,6 +2,6 @@
FactoryBot.define do
factory :user_notification do
checked false
checked { false }
end
end

View file

@ -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

View file

@ -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

View file

@ -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