mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-08 22:24:23 +08:00
Merge pull request #338 from biosistemika/office_integration
Office integration
This commit is contained in:
commit
210519b506
47 changed files with 2239 additions and 663 deletions
3
Gemfile
3
Gemfile
|
@ -68,9 +68,10 @@ gem 'tinymce-rails' # Rich text editor
|
|||
|
||||
gem 'base62' # Used for smart annotations
|
||||
|
||||
gem 'nokogiri' # XML parser
|
||||
|
||||
group :development, :test do
|
||||
gem 'byebug'
|
||||
gem 'web-console', '~> 2.0'
|
||||
gem 'better_errors'
|
||||
gem 'binding_of_caller'
|
||||
gem 'awesome_print'
|
||||
|
|
|
@ -320,11 +320,6 @@ GEM
|
|||
unicode-display_width (1.1.0)
|
||||
warden (1.2.6)
|
||||
rack (>= 1.0)
|
||||
web-console (2.2.1)
|
||||
activemodel (>= 4.0)
|
||||
binding_of_caller (>= 0.7.2)
|
||||
railties (>= 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
wicked_pdf (1.0.3)
|
||||
wkhtmltopdf-heroku (2.12.3.0)
|
||||
yomu (0.2.4)
|
||||
|
@ -403,7 +398,6 @@ DEPENDENCIES
|
|||
tzinfo-data
|
||||
uglifier (>= 1.3.0)
|
||||
underscore-rails
|
||||
web-console (~> 2.0)
|
||||
wicked_pdf
|
||||
wkhtmltopdf-heroku
|
||||
yomu
|
||||
|
|
|
@ -33,6 +33,10 @@ function setupAssetsLoading() {
|
|||
data['preview-url'] + "'><p>" +
|
||||
data.filename + '</p></a>'
|
||||
);
|
||||
} else if(data.type === "wopi") {
|
||||
$el.html(data['wopi-file-name'] +
|
||||
data['wopi-view'] +
|
||||
data['wopi-edit']);
|
||||
} else {
|
||||
$el.html(
|
||||
"<a href='" + data['download-url'] + "'><p>" +
|
||||
|
|
|
@ -491,6 +491,8 @@ function initImport() {
|
|||
if (data.status === "ok") {
|
||||
// Simply reload page
|
||||
location.reload();
|
||||
} else if (data.status === 'locked') {
|
||||
alert(I18n.t("my_modules.protocols.load_from_file_error_locked"));
|
||||
} else {
|
||||
if (data.status === 'size_too_large') {
|
||||
alert('<%= I18n.t('my_modules.protocols.load_from_file_size_error',
|
||||
|
|
|
@ -67,7 +67,14 @@ function formAjaxResultAsset($form) {
|
|||
Comments.initialize();
|
||||
})
|
||||
.on("ajax:error", function(e, data) {
|
||||
$form.renderFormErrors("result", data.errors, true, e);
|
||||
// This check is here only because of remotipart bug, which returns
|
||||
// HTML instead of JSON, go figure
|
||||
var errors = '';
|
||||
if (data.errors)
|
||||
errors = data.errors;
|
||||
else
|
||||
errors = data.responseJSON.errors;
|
||||
$form.renderFormErrors("result", errors, true, e);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,40 @@
|
|||
class AssetsController < ApplicationController
|
||||
include WopiUtil
|
||||
# include ActionView::Helpers
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ActionView::Context
|
||||
include InputSanitizeHelper
|
||||
include FileIconsHelper
|
||||
include WopiHelper
|
||||
|
||||
before_action :load_vars
|
||||
before_action :check_read_permission, except: :file_present
|
||||
before_action :load_vars, except: :signature
|
||||
before_action :check_read_permission, except: [:signature, :file_present]
|
||||
before_action :check_edit_permission, only: :edit
|
||||
|
||||
# Validates asset and then generates S3 upload posts, because
|
||||
# otherwise untracked files could be uploaded to S3
|
||||
def signature
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
asset = Asset.new(asset_params)
|
||||
if asset.valid?
|
||||
posts = generate_upload_posts asset
|
||||
render json: {
|
||||
posts: posts
|
||||
}
|
||||
else
|
||||
render json: {
|
||||
status: 'error',
|
||||
errors: asset.errors
|
||||
}, status: :bad_request
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def file_present
|
||||
respond_to do |format|
|
||||
|
@ -26,7 +59,10 @@ class AssetsController < ApplicationController
|
|||
length:
|
||||
Constants::FILENAME_TRUNCATION_LENGTH),
|
||||
'download-url' => download_asset_path(@asset),
|
||||
'type' => (@asset.is_image? ? 'image' : 'file')
|
||||
'type' => asset_data_type(@asset),
|
||||
'wopi-file-name' => wopi_asset_file_name(@asset),
|
||||
'wopi-edit' => (wopi_asset_edit_button(@asset) if wopi_file?(@asset)),
|
||||
'wopi-view' => (wopi_asset_view_button(@asset) if wopi_file?(@asset))
|
||||
}, status: 200
|
||||
end
|
||||
end
|
||||
|
@ -59,20 +95,39 @@ class AssetsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@action_url = append_wd_params(@asset
|
||||
.get_action_url(current_user, 'edit', false))
|
||||
@favicon_url = @asset.favicon_url('edit')
|
||||
tkn = current_user.get_wopi_token
|
||||
@token = tkn.token
|
||||
@ttl = (tkn.ttl * 1000).to_s
|
||||
create_wopi_file_activity(current_user, true)
|
||||
|
||||
render layout: false
|
||||
end
|
||||
|
||||
def view
|
||||
@action_url = append_wd_params(@asset
|
||||
.get_action_url(current_user, 'view', false))
|
||||
@favicon_url = @asset.favicon_url('view')
|
||||
tkn = current_user.get_wopi_token
|
||||
@token = tkn.token
|
||||
@ttl = (tkn.ttl * 1000).to_s
|
||||
|
||||
render layout: false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
@asset = Asset.find_by_id(params[:id])
|
||||
|
||||
unless @asset
|
||||
render_404
|
||||
end
|
||||
render_404 unless @asset
|
||||
|
||||
step_assoc = @asset.step
|
||||
result_assoc = @asset.result
|
||||
|
||||
@assoc = step_assoc if not step_assoc.nil?
|
||||
@assoc = result_assoc if not result_assoc.nil?
|
||||
@assoc = step_assoc unless step_assoc.nil?
|
||||
@assoc = result_assoc unless result_assoc.nil?
|
||||
|
||||
if @assoc.class == Step
|
||||
@protocol = @asset.step.protocol
|
||||
|
@ -93,9 +148,75 @@ class AssetsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def check_edit_permission
|
||||
if @assoc.class == Step
|
||||
unless can_edit_step_in_protocol(@protocol)
|
||||
render_403 and return
|
||||
end
|
||||
elsif @assoc.class == Result
|
||||
unless can_edit_result_asset_in_module(@my_module)
|
||||
render_403 and return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def generate_upload_posts(asset)
|
||||
posts = []
|
||||
s3_post = S3_BUCKET.presigned_post(
|
||||
key: asset.file.path[1..-1],
|
||||
success_action_status: '201',
|
||||
acl: 'private',
|
||||
storage_class: "STANDARD",
|
||||
content_length_range: 1..Constants::FILE_MAX_SIZE_MB.megabytes,
|
||||
content_type: asset.file_content_type
|
||||
)
|
||||
posts.push({
|
||||
url: s3_post.url,
|
||||
fields: s3_post.fields
|
||||
})
|
||||
|
||||
condition = %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}}
|
||||
|
||||
if condition === asset.file_content_type
|
||||
asset.file.options[:styles].each do |style, option|
|
||||
s3_post = S3_BUCKET.presigned_post(
|
||||
key: asset.file.path(style)[1..-1],
|
||||
success_action_status: '201',
|
||||
acl: 'public-read',
|
||||
storage_class: "REDUCED_REDUNDANCY",
|
||||
content_length_range: 1..Constants::FILE_MAX_SIZE_MB.megabytes,
|
||||
content_type: asset.file_content_type
|
||||
)
|
||||
posts.push({
|
||||
url: s3_post.url,
|
||||
fields: s3_post.fields,
|
||||
style_option: option,
|
||||
mime_type: asset.file_content_type
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
posts
|
||||
end
|
||||
|
||||
def append_wd_params(url)
|
||||
wd_params = ''
|
||||
params.keys.select { |i| i[/^wd.*/] }.each do |wd|
|
||||
next if wd == 'wdPreviousSession' || wd == 'wdPreviousCorrelation'
|
||||
wd_params += "&#{wd}=#{params[wd]}"
|
||||
end
|
||||
url + wd_params
|
||||
end
|
||||
|
||||
def asset_params
|
||||
params.permit(
|
||||
:file
|
||||
)
|
||||
end
|
||||
|
||||
def asset_data_type(asset)
|
||||
return 'wopi' if wopi_file?(asset)
|
||||
return 'image' if asset.is_image?
|
||||
'file'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -339,147 +339,177 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
def revert
|
||||
respond_to do |format|
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
# Revert is basically update from parent
|
||||
@protocol.update_from_parent(current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
if @protocol.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
# Revert is basically update from parent
|
||||
@protocol.update_from_parent(current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
render json: {
|
||||
message: t("my_modules.protocols.revert_error")
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json do
|
||||
render json: {
|
||||
message: t('my_modules.protocols.revert_error')
|
||||
},
|
||||
status: :bad_request
|
||||
end
|
||||
else
|
||||
# Everything good, display flash & render 200
|
||||
flash[:success] = t(
|
||||
'my_modules.protocols.revert_flash'
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
end
|
||||
else
|
||||
# Everything good, display flash & render 200
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.revert_flash",
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
format.json do
|
||||
render json: {
|
||||
message: t('my_modules.protocols.revert_error_locked')
|
||||
}, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_parent
|
||||
respond_to do |format|
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.update_parent(current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
if @protocol.parent.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.update_parent(current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
render json: {
|
||||
message: t("my_modules.protocols.update_parent_error")
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
type_of: :revert_protocol,
|
||||
project: @protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.revert_protocol',
|
||||
user: current_user.full_name,
|
||||
protocol: @protocol.name
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
render json: {
|
||||
message: t("my_modules.protocols.update_parent_error")
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
type_of: :revert_protocol,
|
||||
project: @protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.revert_protocol',
|
||||
user: current_user.full_name,
|
||||
protocol: @protocol.name
|
||||
)
|
||||
)
|
||||
)
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.update_parent_flash",
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.update_parent_flash",
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
render json: {
|
||||
message: t('my_modules.protocols.update_parent_error_locked')
|
||||
}, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_from_parent
|
||||
respond_to do |format|
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.update_from_parent(current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
if @protocol.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.update_from_parent(current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
render json: {
|
||||
message: t("my_modules.protocols.update_from_parent_error")
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
render json: {
|
||||
message: t("my_modules.protocols.update_from_parent_error")
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
else
|
||||
# Everything good, display flash & render 200
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.update_from_parent_flash",
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
end
|
||||
else
|
||||
# Everything good, display flash & render 200
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.update_from_parent_flash",
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
format.json do
|
||||
render json: {
|
||||
message: t('my_modules.protocols.update_from_parent_error_locked')
|
||||
}, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_from_repository
|
||||
respond_to do |format|
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.load_from_repository(@source, current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
if @protocol.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.load_from_repository(@source, current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
render json: {
|
||||
message: t("my_modules.protocols.load_from_repository_error")
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
type_of: :load_protocol_from_repository,
|
||||
project: @protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.load_protocol_from_repository',
|
||||
user: current_user.full_name,
|
||||
protocol: @source.name
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json do
|
||||
render json: {
|
||||
message: t('my_modules.protocols.load_from_repository_error')
|
||||
},
|
||||
status: :bad_request
|
||||
end
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
type_of: :load_protocol_from_repository,
|
||||
project: @protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.load_protocol_from_repository',
|
||||
user: current_user.full_name,
|
||||
protocol: @source.name
|
||||
)
|
||||
)
|
||||
)
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.load_from_repository_flash",
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
flash[:success] = t('my_modules.protocols.load_from_repository_flash')
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
render json: {
|
||||
message: t('my_modules.protocols.load_from_repository_error_locked')
|
||||
}, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -487,40 +517,46 @@ class ProtocolsController < ApplicationController
|
|||
def load_from_file
|
||||
# This is actually very similar to import
|
||||
respond_to do |format|
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
import_into_existing(@protocol, @protocol_json, current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
if @protocol.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
import_into_existing(@protocol, @protocol_json, current_user)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
format.json {
|
||||
render json: { status: :error }, status: :bad_request
|
||||
}
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
type_of: :load_protocol_from_file,
|
||||
project: @protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.load_protocol_from_file',
|
||||
user: current_user.full_name,
|
||||
protocol: @protocol_json[:name]
|
||||
if transaction_error
|
||||
format.json {
|
||||
render json: { status: :error }, status: :bad_request
|
||||
}
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
type_of: :load_protocol_from_file,
|
||||
project: @protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.load_protocol_from_file',
|
||||
user: current_user.full_name,
|
||||
protocol: @protocol_json[:name]
|
||||
)
|
||||
)
|
||||
)
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.load_from_file_flash",
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json {
|
||||
render json: { status: :ok }, status: :ok
|
||||
}
|
||||
flash[:success] = t(
|
||||
'my_modules.protocols.load_from_file_flash',
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json {
|
||||
render json: { status: :ok }, status: :ok
|
||||
}
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
render json: { status: :locked }, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,26 +55,26 @@ class ResultAssetsController < ApplicationController
|
|||
)
|
||||
)
|
||||
|
||||
format.html {
|
||||
format.html do
|
||||
flash[:success] = t(
|
||||
"result_assets.create.success_flash",
|
||||
module: @my_module.name)
|
||||
redirect_to results_my_module_path(@my_module)
|
||||
}
|
||||
format.json {
|
||||
end
|
||||
format.json do
|
||||
render json: {
|
||||
status: 'ok',
|
||||
html: render_to_string({
|
||||
partial: "my_modules/result.html.erb", locals: {result: @result}
|
||||
})
|
||||
html: render_to_string(
|
||||
partial: 'my_modules/result.html.erb', locals: { result: @result }
|
||||
)
|
||||
}, status: :ok
|
||||
}
|
||||
end
|
||||
else
|
||||
# This response is sent as 200 OK due to IE security error when
|
||||
# accessing iframe content.
|
||||
format.json {
|
||||
render json: {status: 'error', errors: @result.errors}
|
||||
}
|
||||
format.json do
|
||||
render json: { status: 'error', errors: @result.errors }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -92,6 +92,7 @@ class ResultAssetsController < ApplicationController
|
|||
def update
|
||||
update_params = result_params
|
||||
previous_size = @result.space_taken
|
||||
previous_asset = @result.asset
|
||||
|
||||
if update_params.key? :asset_attributes
|
||||
asset = Asset.find_by_id(update_params[:asset_attributes][:id])
|
||||
|
@ -106,6 +107,16 @@ class ResultAssetsController < ApplicationController
|
|||
module: @my_module.name)
|
||||
|
||||
if @result.archived_changed?(from: false, to: true)
|
||||
if previous_asset.locked?
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:error] = t('result_assets.archive.error_flash')
|
||||
redirect_to results_my_module_path(@my_module)
|
||||
return
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
saved = @result.archive(current_user)
|
||||
success_flash = t("result_assets.archive.success_flash",
|
||||
module: @my_module.name)
|
||||
|
@ -125,6 +136,19 @@ class ResultAssetsController < ApplicationController
|
|||
elsif @result.archived_changed?(from: true, to: false)
|
||||
render_403
|
||||
else
|
||||
if previous_asset.locked?
|
||||
@result.errors.add(:asset_attributes,
|
||||
t('result_assets.edit.locked_file_error'))
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
status: 'error',
|
||||
errors: @result.errors
|
||||
}, status: :bad_request
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
# Asset (file) and/or name has been changed
|
||||
saved = @result.save
|
||||
|
||||
|
|
|
@ -185,29 +185,38 @@ class StepsController < ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
# Update position on other steps of this module
|
||||
@protocol.steps.where("position > ?", @step.position).each do |step|
|
||||
step.position = step.position - 1
|
||||
step.save
|
||||
if @step.can_destroy?
|
||||
# Update position on other steps of this module
|
||||
@protocol.steps.where('position > ?', @step.position).each do |step|
|
||||
step.position = step.position - 1
|
||||
step.save
|
||||
end
|
||||
|
||||
# Calculate space taken by this step
|
||||
team = @protocol.team
|
||||
previous_size = @step.space_taken
|
||||
|
||||
# Destroy the step
|
||||
@step.destroy(current_user)
|
||||
|
||||
# Release space taken by the step
|
||||
team.release_space(previous_size)
|
||||
team.save
|
||||
|
||||
# Update protocol timestamp
|
||||
update_protocol_ts(@step)
|
||||
|
||||
flash[:success] = t(
|
||||
'protocols.steps.destroy.success_flash',
|
||||
step: (@step.position + 1).to_s
|
||||
)
|
||||
else
|
||||
flash[:error] = t(
|
||||
'protocols.steps.destroy.error_flash',
|
||||
step: (@step.position + 1).to_s
|
||||
)
|
||||
end
|
||||
|
||||
# Calculate space taken by this step
|
||||
team = @protocol.team
|
||||
previous_size = @step.space_taken
|
||||
|
||||
# Destroy the step
|
||||
@step.destroy(current_user)
|
||||
|
||||
# Release space taken by the step
|
||||
team.release_space(previous_size)
|
||||
team.save
|
||||
|
||||
# Update protocol timestamp
|
||||
update_protocol_ts(@step)
|
||||
|
||||
flash[:success] = t(
|
||||
"protocols.steps.destroy.success_flash",
|
||||
step: (@step.position + 1).to_s)
|
||||
if @protocol.in_module?
|
||||
redirect_to protocols_my_module_path(@step.my_module)
|
||||
else
|
||||
|
@ -537,7 +546,12 @@ class StepsController < ApplicationController
|
|||
for pos, attrs in params[key] do
|
||||
if attrs[:_destroy] == '1'
|
||||
if attrs[:id].present?
|
||||
attr_params[pos] = { id: attrs[:id], _destroy: '1' }
|
||||
asset = Asset.find_by_id(attrs[:id])
|
||||
if asset.try(&:locked?)
|
||||
asset.errors.add(:base, 'This file is locked.')
|
||||
else
|
||||
attr_params[pos] = { id: attrs[:id], _destroy: '1' }
|
||||
end
|
||||
end
|
||||
params[key].delete(pos)
|
||||
elsif has_destroy_params(params[key][pos])
|
||||
|
|
339
app/controllers/wopi_controller.rb
Normal file
339
app/controllers/wopi_controller.rb
Normal file
|
@ -0,0 +1,339 @@
|
|||
class WopiController < ActionController::Base
|
||||
include WopiUtil
|
||||
include PermissionHelper
|
||||
|
||||
before_action :load_vars, :authenticate_user_from_token!
|
||||
before_action :verify_proof!
|
||||
|
||||
# Only used for checkfileinfo
|
||||
def file_get_endpoint
|
||||
check_file_info
|
||||
end
|
||||
|
||||
def file_contents_get_endpoint
|
||||
# get_file
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
response.body = Paperclip.io_adapters.for(@asset.file).read
|
||||
send_data response.body, disposition: 'inline', content_type: 'text/plain'
|
||||
end
|
||||
|
||||
def post_file_endpoint
|
||||
override = request.headers['X-WOPI-Override']
|
||||
case override
|
||||
when 'GET_LOCK'
|
||||
get_lock
|
||||
when 'PUT_RELATIVE'
|
||||
put_relative
|
||||
when 'LOCK'
|
||||
old_lock = request.headers['X-WOPI-OldLock']
|
||||
if old_lock.nil?
|
||||
lock
|
||||
else
|
||||
unlock_and_relock
|
||||
end
|
||||
when 'UNLOCK'
|
||||
unlock
|
||||
when 'REFRESH_LOCK'
|
||||
refresh_lock
|
||||
when 'GET_SHARE_URL'
|
||||
render nothing: :true, status: 501 and return
|
||||
else
|
||||
render nothing: :true, status: 404 and return
|
||||
end
|
||||
end
|
||||
|
||||
# Only used for putfile
|
||||
def file_contents_post_endpoint
|
||||
logger.warn 'WOPI: post_file_contents called'
|
||||
put_file
|
||||
end
|
||||
|
||||
def check_file_info
|
||||
msg = {
|
||||
BaseFileName: @asset.file_file_name,
|
||||
OwnerId: @asset.created_by_id.to_s,
|
||||
Size: @asset.file_file_size,
|
||||
UserId: @user.id.to_s,
|
||||
Version: @asset.version.to_s,
|
||||
SupportsExtendedLockLength: true,
|
||||
SupportsGetLock: true,
|
||||
SupportsLocks: true,
|
||||
SupportsUpdate: true,
|
||||
# Setting all users to business until we figure out
|
||||
# which should NOT be business
|
||||
LicenseCheckForEditIsEnabled: true,
|
||||
UserFriendlyName: @user.name,
|
||||
UserCanWrite: @can_write,
|
||||
UserCanNotWriteRelative: true,
|
||||
CloseUrl: @close_url,
|
||||
DownloadUrl: url_for(controller: 'assets', action: 'download',
|
||||
id: @asset.id),
|
||||
HostEditUrl: url_for(controller: 'assets', action: 'edit',
|
||||
id: @asset.id),
|
||||
HostViewUrl: url_for(controller: 'assets', action: 'view',
|
||||
id: @asset.id),
|
||||
BreadcrumbBrandName: @breadcrumb_brand_name,
|
||||
BreadcrumbBrandUrl: @breadcrumb_brand_url,
|
||||
BreadcrumbFolderName: @breadcrumb_folder_name,
|
||||
BreadcrumbFolderUrl: @breadcrumb_folder_url
|
||||
}
|
||||
response.headers['X-WOPI-HostEndpoint'] = ENV['WOPI_ENDPOINT_URL']
|
||||
response.headers['X-WOPI-MachineName'] = ENV['WOPI_ENDPOINT_URL']
|
||||
response.headers['X-WOPI-ServerVersion'] = Constants::APP_VERSION
|
||||
render json: msg and return
|
||||
end
|
||||
|
||||
def put_relative
|
||||
render nothing: :true, status: 501 and return
|
||||
end
|
||||
|
||||
def lock
|
||||
lock = request.headers['X-WOPI-Lock']
|
||||
logger.warn 'WOPI: lock; ' + lock.to_s
|
||||
render nothing: :true, status: 404 and return if lock.nil? || lock.blank?
|
||||
@asset.with_lock do
|
||||
if @asset.locked?
|
||||
if @asset.lock == lock
|
||||
@asset.refresh_lock
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
render nothing: :true, status: 200 and return
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
render nothing: :true, status: 409 and return
|
||||
end
|
||||
else
|
||||
@asset.lock_asset(lock)
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
render nothing: :true, status: 200 and return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unlock_and_relock
|
||||
logger.warn 'lock and relock'
|
||||
lock = request.headers['X-WOPI-Lock']
|
||||
old_lock = request.headers['X-WOPI-OldLock']
|
||||
if lock.nil? || lock.blank? || old_lock.blank?
|
||||
render nothing: :true, status: 400 and return
|
||||
end
|
||||
@asset.with_lock do
|
||||
if @asset.locked?
|
||||
if @asset.lock == old_lock
|
||||
@asset.unlock
|
||||
@asset.lock_asset(lock)
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
render nothing: :true, status: 200 and return
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
render nothing: :true, status: 409 and return
|
||||
end
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = ' '
|
||||
render nothing: :true, status: 409 and return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unlock
|
||||
lock = request.headers['X-WOPI-Lock']
|
||||
render nothing: :true, status: 400 and return if lock.nil? || lock.blank?
|
||||
@asset.with_lock do
|
||||
if @asset.locked?
|
||||
logger.warn "WOPI: current asset lock: #{@asset.lock},
|
||||
unlocking lock #{lock}"
|
||||
if @asset.lock == lock
|
||||
@asset.unlock
|
||||
@asset.post_process_file # Space is already taken in put_file
|
||||
create_wopi_file_activity(@user, false)
|
||||
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
render nothing: :true, status: 200 and return
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
render nothing: :true, status: 409 and return
|
||||
end
|
||||
else
|
||||
logger.warn 'WOPI: tried to unlock non-locked file'
|
||||
response.headers['X-WOPI-Lock'] = ' '
|
||||
render nothing: :true, status: 409 and return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_lock
|
||||
lock = request.headers['X-WOPI-Lock']
|
||||
render nothing: :true, status: 400 and return if lock.nil? || lock.blank?
|
||||
@asset.with_lock do
|
||||
if @asset.locked?
|
||||
if @asset.lock == lock
|
||||
@asset.refresh_lock
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
render nothing: :true, status: 200 and return
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
render nothing: :true, status: 409 and return
|
||||
end
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = ' '
|
||||
render nothing: :true, status: 409 and return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_lock
|
||||
@asset.with_lock do
|
||||
if @asset.locked?
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
else
|
||||
response.headers['X-WOPI-Lock'] = ' '
|
||||
end
|
||||
render nothing: :true, status: 200 and return
|
||||
end
|
||||
end
|
||||
|
||||
def put_file
|
||||
@asset.with_lock do
|
||||
lock = request.headers['X-WOPI-Lock']
|
||||
if @asset.locked?
|
||||
if @asset.lock == lock
|
||||
logger.warn 'WOPI: replacing file'
|
||||
|
||||
@organization.release_space(@asset.estimated_size)
|
||||
@asset.update_contents(request.body)
|
||||
@asset.last_modified_by = @user
|
||||
@asset.save
|
||||
|
||||
@organization.take_space(@asset.estimated_size)
|
||||
@organization.save
|
||||
|
||||
@protocol.update(updated_at: Time.now) if @protocol
|
||||
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
render nothing: :true, status: 200 and return
|
||||
else
|
||||
logger.warn 'WOPI: wrong lock used to try and modify file'
|
||||
response.headers['X-WOPI-Lock'] = @asset.lock
|
||||
render nothing: :true, status: 409 and return
|
||||
end
|
||||
elsif !@asset.file_file_size.nil? && @asset.file_file_size.zero?
|
||||
logger.warn 'WOPI: initializing empty file'
|
||||
|
||||
@organization.release_space(@asset.estimated_size)
|
||||
@asset.update_contents(request.body)
|
||||
@asset.last_modified_by = @user
|
||||
@asset.save
|
||||
@organization.save
|
||||
|
||||
response.headers['X-WOPI-ItemVersion'] = @asset.version
|
||||
render nothing: :true, status: 200 and return
|
||||
else
|
||||
logger.warn 'WOPI: trying to modify unlocked file'
|
||||
response.headers['X-WOPI-Lock'] = ' '
|
||||
render nothing: :true, status: 409 and return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@asset = Asset.find_by_id(params[:id])
|
||||
if @asset.nil?
|
||||
render nothing: :true, status: 404 and return
|
||||
else
|
||||
logger.warn 'Found asset: ' + @asset.id.to_s
|
||||
step_assoc = @asset.step
|
||||
result_assoc = @asset.result
|
||||
@assoc = step_assoc unless step_assoc.nil?
|
||||
@assoc = result_assoc unless result_assoc.nil?
|
||||
|
||||
if @assoc.class == Step
|
||||
@protocol = @asset.step.protocol
|
||||
@organization = @protocol.organization
|
||||
else
|
||||
@my_module = @assoc.my_module
|
||||
@organization = @my_module.experiment.project.organization
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user_from_token!
|
||||
wopi_token = params[:access_token]
|
||||
if wopi_token.nil?
|
||||
logger.warn 'WOPI: nil wopi token'
|
||||
render nothing: :true, status: 401 and return
|
||||
end
|
||||
|
||||
@user = User.find_by_valid_wopi_token(wopi_token)
|
||||
if @user.nil?
|
||||
logger.warn 'WOPI: no user with this token found'
|
||||
render nothing: :true, status: 401 and return
|
||||
end
|
||||
logger.warn 'WOPI: user found by token ' + wopi_token +
|
||||
' ID: ' + @user.id.to_s
|
||||
|
||||
# This is what we get for settings permission methods with
|
||||
# current_user
|
||||
@current_user = @user
|
||||
if @assoc.class == Step
|
||||
@can_read = can_view_steps_in_protocol(@protocol)
|
||||
@can_write = can_edit_step_in_protocol(@protocol)
|
||||
|
||||
if @protocol.in_module?
|
||||
@close_url = protocols_my_module_path(@protocol.my_module,
|
||||
only_path: false)
|
||||
|
||||
project = @protocol.my_module.experiment.project
|
||||
@breadcrumb_brand_name = project.name
|
||||
@breadcrumb_brand_url = project_path(project, only_path: false)
|
||||
@breadcrumb_folder_name = @protocol.my_module.name
|
||||
else
|
||||
@close_url = protocols_path(only_path: false)
|
||||
|
||||
@breadcrump_brand_name = 'Projects'
|
||||
@breadcrumb_brand_url = root_path(only_path: false)
|
||||
@breadcrumb_folder_name = 'Protocol managament'
|
||||
end
|
||||
@breadcrumb_folder_url = @close_url
|
||||
else
|
||||
@can_read = can_view_or_download_result_assets(@my_module)
|
||||
@can_write = can_edit_result_asset_in_module(@my_module)
|
||||
|
||||
@close_url = results_my_module_path(@my_module, only_path: false)
|
||||
|
||||
@breadcrumb_brand_name = @my_module.experiment.project.name
|
||||
@breadcrumb_brand_url = project_path(@my_module.experiment.project,
|
||||
only_path: false)
|
||||
@breadcrumb_folder_name = @my_module.name
|
||||
@breadcrumb_folder_url = @close_url
|
||||
end
|
||||
|
||||
render nothing: :true, status: 404 and return unless @can_read
|
||||
end
|
||||
|
||||
def verify_proof!
|
||||
token = params[:access_token].encode('utf-8')
|
||||
timestamp = request.headers['X-WOPI-TimeStamp'].to_i
|
||||
signed_proof = request.headers['X-WOPI-Proof']
|
||||
signed_proof_old = request.headers['X-WOPI-ProofOld']
|
||||
url = request.original_url.upcase.encode('utf-8')
|
||||
|
||||
if convert_to_unix_timestamp(timestamp) + 20.minutes >= Time.now
|
||||
if current_wopi_discovery.verify_proof(token, timestamp, signed_proof,
|
||||
signed_proof_old, url)
|
||||
logger.warn 'WOPI: proof verification: successful'
|
||||
else
|
||||
logger.warn 'WOPI: proof verification: not verified'
|
||||
render nothing: :true, status: 500 and return
|
||||
end
|
||||
else
|
||||
logger.warn 'WOPI: proof verification: timestamp too old; ' +
|
||||
timestamp.to_s
|
||||
render nothing: :true, status: 500 and return
|
||||
end
|
||||
rescue => e
|
||||
logger.warn 'WOPI: proof verification: failed; ' + e.message
|
||||
render nothing: :true, status: 500 and return
|
||||
end
|
||||
end
|
60
app/helpers/file_icons_helper.rb
Normal file
60
app/helpers/file_icons_helper.rb
Normal file
|
@ -0,0 +1,60 @@
|
|||
module FileIconsHelper
|
||||
def wopi_file?(asset)
|
||||
file_ext = asset.file_file_name.split('.').last
|
||||
%w(csv ods xls xlsb xlsm xlsx odp pot potm potx pps ppsm ppsx ppt pptm pptx doc docm docx dot dotm dotx odt rtf).include?(file_ext)
|
||||
end
|
||||
|
||||
# For showing next to file
|
||||
def file_extension_icon(asset)
|
||||
file_ext = asset.file_file_name.split('.').last
|
||||
if %w(doc docm docx dot dotm dotx odt rtf).include?(file_ext)
|
||||
image_link = 'office/Word-docx_20x20x32.png'
|
||||
elsif %w(csv ods xls xlsb xlsm xlsx).include?(file_ext)
|
||||
image_link = 'office/Excel-xlsx_20x20x32.png'
|
||||
elsif %w(odp pot potm potx pps ppsm ppsx ppt pptm pptx).include?(file_ext)
|
||||
image_link = 'office/PowerPoint-pptx_20x20x32.png'
|
||||
end
|
||||
|
||||
if image_link
|
||||
image_tag image_link
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# For showing in view/edit buttons (WOPI)
|
||||
def file_application_icon(asset)
|
||||
file_ext = asset.file_file_name.split('.').last
|
||||
if %w(doc docm docx dot dotm dotx odt rtf).include?(file_ext)
|
||||
image_link = 'office/Word-color_16x16x32.png'
|
||||
elsif %w(csv ods xls xlsb xlsm xlsx).include?(file_ext)
|
||||
image_link = 'office/Excel-color_16x16x32.png'
|
||||
elsif %w(odp pot potm potx pps ppsm ppsx ppt pptm pptx).include?(file_ext)
|
||||
image_link = 'office/PowerPoint-Color_16x16x32.png'
|
||||
end
|
||||
|
||||
if image_link
|
||||
image_tag image_link
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Shows correct WOPI application text (Word Online/Excel ..)
|
||||
def wopi_button_text(asset, action)
|
||||
file_ext = asset.file_file_name.split('.').last
|
||||
if %w(doc docm docx dot dotm dotx odt rtf).include?(file_ext)
|
||||
app = 'Word Online'
|
||||
elsif %w(csv ods xls xlsb xlsm xlsx).include?(file_ext)
|
||||
app = 'Excel Online'
|
||||
elsif %w(odp pot potm potx pps ppsm ppsx ppt pptm pptx).include?(file_ext)
|
||||
app = 'PowerPoint Online'
|
||||
end
|
||||
|
||||
if action == 'view'
|
||||
t('result_assets.wopi_open_file', app: app)
|
||||
elsif action == 'edit'
|
||||
t('result_assets.wopi_edit_file', app: app)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -39,6 +39,14 @@ module ResultsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def result_unlocked?(result)
|
||||
if result.is_asset
|
||||
!result.asset.locked?
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def result_path_of_type(result)
|
||||
if result.is_asset
|
||||
result_asset_path(result.result_asset)
|
||||
|
|
62
app/helpers/wopi_helper.rb
Normal file
62
app/helpers/wopi_helper.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
module WopiHelper
|
||||
def wopi_result_view_file_button(result)
|
||||
if result.asset.can_perform_action('view')
|
||||
link_to view_asset_url(id: result.asset),
|
||||
class: 'btn btn-default btn-sm',
|
||||
target: '_blank',
|
||||
style: 'display: inline-block' do
|
||||
"#{file_application_icon(
|
||||
result.asset
|
||||
)} #{wopi_button_text(result.asset, 'view')}".html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def wopi_result_edit_file_button(result)
|
||||
if can_edit_result_asset_in_module(result.my_module) &&
|
||||
result.asset.can_perform_action('edit')
|
||||
link_to edit_asset_url(id: result.asset),
|
||||
class: 'btn btn-default btn-sm',
|
||||
target: '_blank',
|
||||
style: 'display: inline-block' do
|
||||
"#{file_application_icon(
|
||||
result.asset
|
||||
)} #{wopi_button_text(result.asset, 'edit')}".html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def wopi_asset_view_button(asset)
|
||||
if asset.can_perform_action('view')
|
||||
link_to view_asset_url(id: asset),
|
||||
class: 'btn btn-default btn-sm',
|
||||
target: '_blank',
|
||||
style: 'display: inline-block' do
|
||||
"#{file_application_icon(asset)} #{wopi_button_text(asset, 'view')}"
|
||||
.html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def wopi_asset_edit_button(asset)
|
||||
if asset.can_perform_action('edit')
|
||||
link_to edit_asset_url(id: asset),
|
||||
class: 'btn btn-default btn-sm',
|
||||
target: '_blank',
|
||||
style: 'display: inline-block' do
|
||||
"#{file_application_icon(
|
||||
asset
|
||||
)} #{wopi_button_text(asset, 'edit')}".html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def wopi_asset_file_name(asset)
|
||||
html = '<p style="display: inline-block">'
|
||||
html += "#{file_extension_icon(asset)} "
|
||||
html += truncate(asset.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH)
|
||||
html += ' </p>'
|
||||
sanitize_input(html, %w(img))
|
||||
end
|
||||
end
|
|
@ -45,6 +45,8 @@ class Activity < ActiveRecord::Base
|
|||
:edit_result_comment,
|
||||
:delete_result_comment,
|
||||
:destroy_result,
|
||||
:start_edit_wopi_file,
|
||||
:unlock_wopi_file,
|
||||
:load_protocol_from_file,
|
||||
:load_protocol_from_repository,
|
||||
:revert_protocol,
|
||||
|
|
|
@ -2,8 +2,11 @@ class Asset < ActiveRecord::Base
|
|||
include SearchableModel
|
||||
include DatabaseHelper
|
||||
include Encryptor
|
||||
include WopiUtil
|
||||
|
||||
require 'tempfile'
|
||||
# Lock duration set to 30 minutes
|
||||
LOCK_DURATION = 60*30
|
||||
|
||||
# Paperclip validation
|
||||
has_attached_file :file,
|
||||
|
@ -305,9 +308,12 @@ class Asset < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Preserving attachments (on client-side) between failed validations (only usable for small/few files!)
|
||||
# Needs to be called before save method and view needs to have :file_content and :file_info hidden field
|
||||
# If file is an image, it can be viewed on front-end using @preview_cached with image_tag tag
|
||||
# Preserving attachments (on client-side) between failed validations
|
||||
# (only usable for small/few files!).
|
||||
# Needs to be called before save method and view needs to have
|
||||
# :file_content and :file_info hidden field.
|
||||
# If file is an image, it can be viewed on front-end
|
||||
# using @preview_cached with image_tag tag.
|
||||
def preserve(file_data)
|
||||
if file_data[:file_content].present?
|
||||
restore_cached(file_data[:file_content], file_data[:file_info])
|
||||
|
@ -315,6 +321,109 @@ class Asset < ActiveRecord::Base
|
|||
cache
|
||||
end
|
||||
|
||||
def can_perform_action(action)
|
||||
if ENV['WOPI_ENABLED'] == 'true'
|
||||
file_ext = file_file_name.split('.').last
|
||||
|
||||
if file_ext == 'wopitest' &&
|
||||
(!ENV['WOPI_TEST_ENABLED'] || ENV['WOPI_TEST_ENABLED'] == 'false')
|
||||
return false
|
||||
end
|
||||
action = get_action(file_ext, action)
|
||||
return false if action.nil?
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def get_action_url(user, action, with_tokens = true)
|
||||
file_ext = file_file_name.split('.').last
|
||||
action = get_action(file_ext, action)
|
||||
if !action.nil?
|
||||
action_url = action.urlsrc
|
||||
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/,
|
||||
'IsLicensedUser=0&')
|
||||
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER>/,
|
||||
'IsLicensedUser=0')
|
||||
action_url = action_url.gsub(/<.*?=.*?>/, '')
|
||||
|
||||
rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url(
|
||||
host: ENV['WOPI_ENDPOINT_URL'],
|
||||
id: id
|
||||
)
|
||||
action_url += "WOPISrc=#{rest_url}"
|
||||
if with_tokens
|
||||
token = user.get_wopi_token
|
||||
action_url + "&access_token=#{token.token}"\
|
||||
"&access_token_ttl=#{(token.ttl * 1000)}"
|
||||
else
|
||||
action_url
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def favicon_url(action)
|
||||
file_ext = file_file_name.split('.').last
|
||||
action = get_action(file_ext, action)
|
||||
action.wopi_app.icon if action.try(:wopi_app)
|
||||
end
|
||||
|
||||
# locked?, lock_asset and refresh_lock rely on the asset
|
||||
# being locked in the database to prevent race conditions
|
||||
def locked?
|
||||
!lock.nil?
|
||||
end
|
||||
|
||||
def lock_asset(lock_string)
|
||||
self.lock = lock_string
|
||||
self.lock_ttl = Time.now.to_i + LOCK_DURATION
|
||||
delay(queue: :assets, run_at: LOCK_DURATION.seconds.from_now).unlock_expired
|
||||
save!
|
||||
end
|
||||
|
||||
def refresh_lock
|
||||
self.lock_ttl = Time.now.to_i + LOCK_DURATION
|
||||
delay(queue: :assets, run_at: LOCK_DURATION.seconds.from_now).unlock_expired
|
||||
save!
|
||||
end
|
||||
|
||||
def unlock
|
||||
self.lock = nil
|
||||
self.lock_ttl = nil
|
||||
save!
|
||||
end
|
||||
|
||||
def unlock_expired
|
||||
with_lock do
|
||||
if !lock_ttl.nil? && lock_ttl >= Time.now.to_i
|
||||
self.lock = nil
|
||||
self.lock_ttl = nil
|
||||
save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_contents(new_file)
|
||||
new_file.class.class_eval { attr_accessor :original_filename }
|
||||
new_file.original_filename = file_file_name
|
||||
self.file = new_file
|
||||
self.version = version.nil? ? 1 : version + 1
|
||||
save
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Checks if attachments is an image (in post processing imagemagick will
|
||||
# generate styles)
|
||||
def allow_styles_on_images
|
||||
if !(file.content_type =~ %r{^(image|(x-)?application)/(x-png|pjpeg|jpeg|jpg|png|gif)$})
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_paperclip_errors
|
||||
|
|
|
@ -417,6 +417,8 @@ class Protocol < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def archive(user)
|
||||
return nil unless can_destroy?
|
||||
|
||||
# Don't update "updated_at" timestamp
|
||||
self.record_timestamps = false
|
||||
|
||||
|
@ -637,6 +639,10 @@ class Protocol < ActiveRecord::Base
|
|||
self.reload
|
||||
end
|
||||
|
||||
def can_destroy?
|
||||
steps.map(&:can_destroy?).all?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def deep_clone(clone, current_user)
|
||||
|
|
|
@ -83,6 +83,10 @@ class Step < ActiveRecord::Base
|
|||
super()
|
||||
end
|
||||
|
||||
def can_destroy?
|
||||
!assets.map(&:locked?).any?
|
||||
end
|
||||
|
||||
def my_module
|
||||
protocol.present? ? protocol.my_module : nil
|
||||
end
|
||||
|
|
8
app/models/token.rb
Normal file
8
app/models/token.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
class Token < ActiveRecord::Base
|
||||
|
||||
validates :token, presence: true
|
||||
validates :ttl, presence: true
|
||||
|
||||
belongs_to :user, foreign_key: 'user_id', class_name: 'User', inverse_of: :tokens
|
||||
|
||||
end
|
|
@ -145,6 +145,12 @@ class User < ActiveRecord::Base
|
|||
class_name: 'Table',
|
||||
foreign_key: 'last_modified_by_id'
|
||||
has_many :created_tags, class_name: 'Tag', foreign_key: 'created_by_id'
|
||||
|
||||
has_many :tokens,
|
||||
class_name: 'Token',
|
||||
foreign_key: 'user_id',
|
||||
inverse_of: :user
|
||||
|
||||
has_many :modified_tags,
|
||||
class_name: 'Tag',
|
||||
foreign_key: 'last_modified_by_id'
|
||||
|
@ -169,6 +175,7 @@ class User < ActiveRecord::Base
|
|||
class_name: 'Protocol',
|
||||
foreign_key: 'restored_by_id',
|
||||
inverse_of: :restored_by
|
||||
|
||||
has_many :user_notifications, inverse_of: :user
|
||||
has_many :notifications, through: :user_notifications
|
||||
|
||||
|
@ -211,8 +218,8 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
result
|
||||
.where_attributes_like([:full_name, :email], query)
|
||||
.distinct
|
||||
.where_attributes_like([:full_name, :email], query)
|
||||
.distinct
|
||||
end
|
||||
|
||||
def empty_avatar(name, size)
|
||||
|
@ -312,6 +319,33 @@ class User < ActiveRecord::Base
|
|||
.uniq
|
||||
end
|
||||
|
||||
def self.find_by_valid_wopi_token(token)
|
||||
Rails.logger.warn "WOPI: searching by token #{token}"
|
||||
User
|
||||
.joins('LEFT OUTER JOIN tokens ON user_id = users.id')
|
||||
.where(tokens: { token: token })
|
||||
.where('tokens.ttl = 0 OR tokens.ttl > ?', Time.now.to_i)
|
||||
.first
|
||||
end
|
||||
|
||||
def get_wopi_token
|
||||
# WOPI does not have a good way to request a new token,
|
||||
# so a new token should be provided each time this is called,
|
||||
# while keeping any old tokens as long as they have not yet expired
|
||||
tokens = Token.where(user_id: id).distinct
|
||||
|
||||
tokens.each do |token|
|
||||
token.delete if token.ttl < Time.now.to_i
|
||||
end
|
||||
|
||||
token_string = "#{Devise.friendly_token(20)}-#{id}"
|
||||
# WOPI uses millisecond TTLs
|
||||
ttl = (Time.now + 1.day).to_i
|
||||
wopi_token = Token.create(token: token_string, ttl: ttl, user_id: id)
|
||||
Rails.logger.warn("WOPI: generating new token #{wopi_token.token}")
|
||||
wopi_token
|
||||
end
|
||||
|
||||
def teams_ids
|
||||
teams.pluck(:id)
|
||||
end
|
||||
|
|
9
app/models/wopi_action.rb
Normal file
9
app/models/wopi_action.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class WopiAction < ActiveRecord::Base
|
||||
belongs_to :wopi_app, foreign_key: 'wopi_app_id', class_name: 'WopiApp'
|
||||
validates :action, :extension, :urlsrc, :wopi_app, presence: true
|
||||
|
||||
def self.find_action(extension, activity)
|
||||
WopiAction.distinct
|
||||
.where('extension = ? and action = ?', extension, activity).first
|
||||
end
|
||||
end
|
10
app/models/wopi_app.rb
Normal file
10
app/models/wopi_app.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class WopiApp < ActiveRecord::Base
|
||||
belongs_to :wopi_discovery,
|
||||
foreign_key: 'wopi_discovery_id',
|
||||
class_name: 'WopiDiscovery'
|
||||
has_many :wopi_actions,
|
||||
class_name: 'WopiAction',
|
||||
foreign_key: 'wopi_app_id',
|
||||
dependent: :destroy
|
||||
validates :name, :icon, :wopi_discovery, presence: true
|
||||
end
|
53
app/models/wopi_discovery.rb
Normal file
53
app/models/wopi_discovery.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
class WopiDiscovery < ActiveRecord::Base
|
||||
require 'base64'
|
||||
|
||||
has_many :wopi_apps,
|
||||
class_name: 'WopiApp',
|
||||
foreign_key: 'wopi_discovery_id',
|
||||
dependent: :destroy
|
||||
validates :expires,
|
||||
:proof_key_mod,
|
||||
:proof_key_exp,
|
||||
:proof_key_old_mod,
|
||||
:proof_key_old_exp,
|
||||
presence: true
|
||||
|
||||
# Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted
|
||||
# with this discovery public key (two key possible old/new)
|
||||
def verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
|
||||
token_length = [token.length].pack('>N').bytes
|
||||
timestamp_bytes = [timestamp.to_i].pack('>Q').bytes.reverse
|
||||
timestamp_length = [timestamp_bytes.length].pack('>N').bytes
|
||||
url_length = [url.length].pack('>N').bytes
|
||||
|
||||
expected_proof = token_length + token.bytes +
|
||||
url_length + url.upcase.bytes +
|
||||
timestamp_length + timestamp_bytes
|
||||
|
||||
key = generate_key(proof_key_mod, proof_key_exp)
|
||||
old_key = generate_key(proof_key_old_mod, proof_key_old_exp)
|
||||
|
||||
# Try all possible combiniations
|
||||
try_verification(expected_proof, signed_proof, key) ||
|
||||
try_verification(expected_proof, signed_proof_old, key) ||
|
||||
try_verification(expected_proof, signed_proof, old_key)
|
||||
end
|
||||
|
||||
# Generates a public key from given modulus and exponent
|
||||
def generate_key(modulus, exponent)
|
||||
mod = Base64.decode64(modulus).unpack('H*').first.to_i(16)
|
||||
exp = Base64.decode64(exponent).unpack('H*').first.to_i(16)
|
||||
|
||||
seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(mod),
|
||||
OpenSSL::ASN1::Integer.new(exp)])
|
||||
OpenSSL::PKey::RSA.new(seq.to_der)
|
||||
end
|
||||
|
||||
# Verify if decrypting signed_proof with public_key equals to expected_proof
|
||||
def try_verification(expected_proof, signed_proof_b64, public_key)
|
||||
signed_proof = Base64.decode64(signed_proof_b64)
|
||||
public_key.verify(OpenSSL::Digest::SHA256.new, signed_proof,
|
||||
expected_proof.pack('c*'))
|
||||
end
|
||||
|
||||
end
|
25
app/utilities/subdomain.rb
Normal file
25
app/utilities/subdomain.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class UserSubdomain
|
||||
def self.matches?(request)
|
||||
if ENV['USER_SUBDOMAIN']
|
||||
return (request.subdomain.present? &&
|
||||
request.subdomain == ENV['USER_SUBDOMAIN'])
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class WopiSubdomain
|
||||
def self.matches?(request)
|
||||
if ENV['WOPI_ENABLED'] == 'true'
|
||||
if ENV['WOPI_SUBDOMAIN']
|
||||
return (request.subdomain.present? &&
|
||||
request.subdomain == ENV['WOPI_SUBDOMAIN'])
|
||||
else
|
||||
return true
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
112
app/utilities/wopi_util.rb
Normal file
112
app/utilities/wopi_util.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
module WopiUtil
|
||||
require 'open-uri'
|
||||
|
||||
# Used for timestamp
|
||||
UNIX_EPOCH_IN_CLR_TICKS = 621355968000000000
|
||||
CLR_TICKS_PER_SECOND = 10000000
|
||||
|
||||
DISCOVERY_TTL = 1.days
|
||||
DISCOVERY_TTL.freeze
|
||||
|
||||
# For more explanation see this:
|
||||
# http://stackoverflow.com/questions/11888053/
|
||||
# convert-net-datetime-ticks-property-to-date-in-objective-c
|
||||
def convert_to_unix_timestamp(timestamp)
|
||||
Time.at((timestamp - UNIX_EPOCH_IN_CLR_TICKS) / CLR_TICKS_PER_SECOND)
|
||||
end
|
||||
|
||||
def get_action(extension, activity)
|
||||
current_wopi_discovery
|
||||
WopiAction.find_action(extension, activity)
|
||||
end
|
||||
|
||||
def current_wopi_discovery
|
||||
discovery = WopiDiscovery.first
|
||||
return discovery if discovery && discovery.expires >= Time.now.to_i
|
||||
initialize_discovery(discovery)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Currently only saves Excel, Word and PowerPoint view and edit actions
|
||||
def initialize_discovery(discovery)
|
||||
Rails.logger.warn 'Initializing discovery'
|
||||
discovery.destroy if discovery
|
||||
|
||||
@doc = Nokogiri::XML(Kernel.open(ENV['WOPI_DISCOVERY_URL']))
|
||||
|
||||
discovery = WopiDiscovery.new
|
||||
discovery.expires = Time.now.to_i + DISCOVERY_TTL
|
||||
key = @doc.xpath('//proof-key')
|
||||
discovery.proof_key_mod = key.xpath('@modulus').first.value
|
||||
discovery.proof_key_exp = key.xpath('@exponent').first.value
|
||||
discovery.proof_key_old_mod = key.xpath('@oldmodulus').first.value
|
||||
discovery.proof_key_old_exp = key.xpath('@oldexponent').first.value
|
||||
discovery.save!
|
||||
|
||||
@doc.xpath('//app').each do |app|
|
||||
app_name = app.xpath('@name').first.value
|
||||
next unless %w(Excel Word PowerPoint WopiTest).include?(app_name)
|
||||
|
||||
wopi_app = WopiApp.new
|
||||
wopi_app.name = app.xpath('@name').first.value
|
||||
wopi_app.icon = app.xpath('@favIconUrl').first.value
|
||||
wopi_app.wopi_discovery_id = discovery.id
|
||||
wopi_app.save!
|
||||
app.xpath('action').each do |action|
|
||||
name = action.xpath('@name').first.value
|
||||
next unless %w(view edit wopitest).include?(name)
|
||||
wopi_action = WopiAction.new
|
||||
wopi_action.action = name
|
||||
wopi_action.extension = action.xpath('@ext').first.value
|
||||
wopi_action.urlsrc = action.xpath('@urlsrc').first.value
|
||||
wopi_action.wopi_app_id = wopi_app.id
|
||||
wopi_action.save!
|
||||
end
|
||||
end
|
||||
discovery
|
||||
rescue => e
|
||||
Rails.logger.warn 'WOPI: initialization failed: ' + e.message
|
||||
e.backtrace.each { |line| Rails.logger.error line }
|
||||
discovery = WopiDiscovery.first
|
||||
discovery.destroy if discovery
|
||||
end
|
||||
|
||||
def create_wopi_file_activity(current_user, started_editing)
|
||||
if @assoc.class == Step
|
||||
activity = Activity.new(
|
||||
type_of: :start_edit_wopi_file,
|
||||
user: current_user,
|
||||
message: t(
|
||||
started_editing ? 'activities.start_edit_wopi_file_step' :
|
||||
'activities.unlock_wopi_file_step',
|
||||
user: current_user.full_name,
|
||||
file: @asset.file_file_name,
|
||||
step: @asset.step.position + 1,
|
||||
step_name: @asset.step.name
|
||||
)
|
||||
)
|
||||
|
||||
if @protocol.in_module?
|
||||
activity.my_module = @protocol.my_module
|
||||
activity.project = @protocol.my_module.experiment.project
|
||||
end
|
||||
|
||||
activity.save
|
||||
else
|
||||
Activity.create(
|
||||
type_of: :start_edit_wopi_file,
|
||||
user: current_user,
|
||||
project: @my_module.experiment.project,
|
||||
my_module: @my_module,
|
||||
message: t(
|
||||
started_editing ? 'activities.start_edit_wopi_file_result' :
|
||||
'activities.unlock_wopi_file_result',
|
||||
user: current_user.full_name,
|
||||
file: @asset.file_file_name,
|
||||
result: @asset.result.name
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
60
app/views/assets/edit.erb
Normal file
60
app/views/assets/edit.erb
Normal file
|
@ -0,0 +1,60 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- Enable IE Standards mode -->
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
|
||||
<title><%= t('assets.head_title.edit', file_name: @asset.file_file_name) %></title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
|
||||
<link rel="shortcut icon" href='<%= @favicon_url %>' />
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow:hidden;
|
||||
-ms-content-zooming: none;
|
||||
}
|
||||
#office_frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form id='office_form' name='office_form' target='office_frame'
|
||||
action='<%= @action_url %>' method='post'>
|
||||
<input name='access_token' value='<%= @token %>' type='hidden'/>
|
||||
<input name='access_token_ttl' value='<%= @ttl %>' type='hidden'/>
|
||||
</form>
|
||||
|
||||
<span id="frameholder"></span>
|
||||
|
||||
<script type="text/javascript">
|
||||
var frameholder = document.getElementById('frameholder');
|
||||
var office_frame = document.createElement('iframe');
|
||||
office_frame.name = 'office_frame';
|
||||
office_frame.id ='office_frame';
|
||||
// The title should be set for accessibility
|
||||
office_frame.title = 'Office Online Frame';
|
||||
// This attribute allows true fullscreen mode in slideshow view
|
||||
// when using PowerPoint Online's 'view' action.
|
||||
office_frame.setAttribute('allowfullscreen', 'true');
|
||||
frameholder.appendChild(office_frame);
|
||||
document.getElementById('office_form').submit();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
61
app/views/assets/view.erb
Normal file
61
app/views/assets/view.erb
Normal file
|
@ -0,0 +1,61 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- Enable IE Standards mode -->
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
|
||||
<title><%= t('assets.head_title.view', file_name: @asset.file_file_name) %></title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
|
||||
<link rel="shortcut icon" href='<%= @favicon_url %>' />
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow:hidden;
|
||||
-ms-content-zooming: none;
|
||||
}
|
||||
#office_frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form id='office_form' name='office_form' target='office_frame'
|
||||
action='<%= @action_url %>' method='post'>
|
||||
<input name='access_token' value='<%= @token %>' type='hidden'/>
|
||||
<input name='access_token_ttl' value='<%= @ttl %>' type='hidden'/>
|
||||
</form>
|
||||
|
||||
<span id="frameholder"></span>
|
||||
|
||||
<script type="text/javascript">
|
||||
var frameholder = document.getElementById('frameholder');
|
||||
var office_frame = document.createElement('iframe');
|
||||
office_frame.name = 'office_frame';
|
||||
office_frame.id ='office_frame';
|
||||
// The title should be set for accessibility
|
||||
office_frame.title = 'Office Online Frame';
|
||||
// This attribute allows true fullscreen mode in slideshow view
|
||||
// when using PowerPoint Online's 'view' action.
|
||||
office_frame.setAttribute('allowfullscreen', 'true');
|
||||
frameholder.appendChild(office_frame);
|
||||
document.getElementById('office_form').submit();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -18,7 +18,7 @@
|
|||
<span class="glyphicon glyphicon-edit"></span>
|
||||
</a>
|
||||
<% end %>
|
||||
<% if can_archive_result(result) and not result.archived %>
|
||||
<% if can_archive_result(result) && !result.archived && result_unlocked?(result) %>
|
||||
<a href="#" class="btn btn-link form-submit-link" data-submit-form="result-archive-form-<%= result.id %>" data-confirm-form="<%= t('my_modules.results.archive_confirm') %>" title="<%= t'my_modules.results.options.archive_title' %>">
|
||||
<span class="glyphicon glyphicon-briefcase"></span>
|
||||
</a>
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
|
||||
<%= f.fields_for :asset do |ff| %>
|
||||
<span><strong><%=t "result_assets.edit.uploaded_asset" %></strong></span>
|
||||
<p style="margin: 10px;"><%= ff.object.file_file_name %></p>
|
||||
<p style="margin: 10px;">
|
||||
<%= file_extension_icon(ff.object) %>
|
||||
<%= ff.object.file_file_name %>
|
||||
</p>
|
||||
<%= ff.file_field :file %>
|
||||
<% end %>
|
||||
<hr>
|
||||
|
|
|
@ -17,19 +17,15 @@
|
|||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to download_asset_path(result.asset), data: {no_turbolink: true} do %>
|
||||
<p><%= truncate(result.asset.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></p>
|
||||
<% if wopi_file?(result.asset) %>
|
||||
<%= wopi_asset_file_name(result.asset) %>
|
||||
<% else %>
|
||||
<p><%= truncate(result.asset.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= wopi_result_view_file_button(result) %>
|
||||
<%= wopi_result_edit_file_button(result) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<% if result.asset.file.processing? %>
|
||||
<span data-status='asset-loading'
|
||||
data-present-url='<%= file_present_asset_path(result.asset) %>'>
|
||||
<%= image_tag 'medium/processing.gif' %>
|
||||
</span>
|
||||
<% else %>
|
||||
<%= image_tag result.asset.url(:medium) if result.asset.is_image? %>
|
||||
<p><%= result.asset.file_file_name %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
<% if asset.is_image? %>
|
||||
<span class="glyphicon glyphicon-picture"></span>
|
||||
<% else %>
|
||||
<span class="glyphicon glyphicon-file"></span>
|
||||
<% if wopi_file?(asset) %>
|
||||
<%= file_extension_icon(asset) %>
|
||||
<% else %>
|
||||
<span class="glyphicon glyphicon-file"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render partial: "search/results/partials/asset_text.html.erb", locals: { asset: asset, query: search_query } %>
|
||||
</h5>
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
<span class="glyphicon glyphicon-file"></span>
|
||||
<%= t("protocols.steps.new.asset_panel_title") %>
|
||||
<div class="pull-right">
|
||||
<%= ff.remove_nested_fields_link do %>
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
<% unless ff.object.file.exists? && ff.object.locked? %>
|
||||
<%= ff.remove_nested_fields_link do %>
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,15 +19,29 @@
|
|||
id: "modal_link#{ff.object.id}",
|
||||
data: {no_turbolink: true, id: true, status: "asset-present",
|
||||
description: "#{step.position + 1}. #{truncate(step.name, length: Constants::FILENAME_TRUNCATION_LENGTH)}"} do %>
|
||||
<%= image_tag ff.object.url(:medium), data: {'preview-url': large_image_url_asset_path(ff.object)} %>
|
||||
<p><%= truncate(ff.object.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></p>
|
||||
<% if ff.object.is_image? %>
|
||||
<%= image_tag ff.object.url(:medium), data: {'preview-url': large_image_url_asset_path(ff.object)} %>
|
||||
<p><%= truncate(ff.object.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></p>
|
||||
<% else %>
|
||||
<p>
|
||||
<%= file_extension_icon(ff.object) %>
|
||||
<%= ff.object.file_file_name %>
|
||||
</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to download_asset_path(ff.object), data: {no_turbolink: true} do %>
|
||||
<%= image_tag ff.object.url(:medium) if ff.object.is_image? %>
|
||||
<p><%= truncate(ff.object.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></p>
|
||||
<% if ff.object.is_image? %>
|
||||
<%= image_tag ff.object.url(:medium) %>
|
||||
<p><%= truncate(ff.object.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></p>
|
||||
<% else %>
|
||||
<p>
|
||||
<%= file_extension_icon(ff.object) %>
|
||||
<%= ff.object.file_file_name %>
|
||||
</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
|
|
|
@ -6,27 +6,44 @@
|
|||
<div class="panel-heading">
|
||||
<div class="panel-options pull-right">
|
||||
<% if can_reorder_step_in_protocol(@protocol) %>
|
||||
<a data-action="move-step" class="btn btn-link" href="<%= move_up_step_path(step, format: :json) %>" title="<%= t("protocols.steps.options.up_arrow_title") %>" data-remote="true">
|
||||
<a data-action="move-step"
|
||||
class="btn btn-link"
|
||||
href="<%= move_up_step_path(step, format: :json) %>"
|
||||
title="<%= t("protocols.steps.options.up_arrow_title") %>"
|
||||
data-remote="true">
|
||||
<span class="glyphicon glyphicon-arrow-up"></span></a>
|
||||
<a data-action="move-step" class="btn btn-link" href="<%= move_down_step_path(step, format: :json) %>" title="<%= t("protocols.steps.options.down_arrow_title") %>" data-remote="true">
|
||||
<a data-action="move-step"
|
||||
class="btn btn-link"
|
||||
href="<%= move_down_step_path(step, format: :json) %>"
|
||||
title="<%= t("protocols.steps.options.down_arrow_title") %>"
|
||||
data-remote="true">
|
||||
<span class="glyphicon glyphicon-arrow-down"></a>
|
||||
<% end %>
|
||||
<% if can_edit_step_in_protocol(@protocol) %>
|
||||
<a data-action="edit-step" class="btn btn-link" title="<%= t("protocols.steps.options.edit_title") %>" href="<%= edit_step_path(step, format: :json) %>" data-remote="true">
|
||||
<a data-action="edit-step"
|
||||
class="btn btn-link"
|
||||
title="<%= t("protocols.steps.options.edit_title") %>"
|
||||
href="<%= edit_step_path(step, format: :json) %>"
|
||||
data-remote="true">
|
||||
<span class="glyphicon glyphicon-edit">
|
||||
</a>
|
||||
<% end %>
|
||||
<% if can_delete_step_in_protocol(@protocol) %>
|
||||
<% if can_delete_step_in_protocol(@protocol) && step.can_destroy? %>
|
||||
<%= link_to(step_path(step), title: t("protocols.steps.options.delete_title"), method: "delete", class: "btn btn-link",
|
||||
data: {action: "delete-step", confirm: t("protocols.steps.destroy.confirm", step: step.name)}) do %>
|
||||
<span class="glyphicon glyphicon-trash">
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<a class="step-panel-collapse-link" href="#step-panel-<%= step.id %>" data-toggle="collapse" data-remote="true">
|
||||
<a class="step-panel-collapse-link"
|
||||
href="#step-panel-<%= step.id %>"
|
||||
data-toggle="collapse"
|
||||
data-remote="true">
|
||||
<span class="glyphicon glyphicon-collapse-down collapse-step-icon"></span>
|
||||
<strong><%= step.name %></strong> |
|
||||
<span><%= sanitize_input t("protocols.steps.published_on", timestamp: l(step.created_at, format: :full), user: h(step.user.full_name)) %></span>
|
||||
<span><%= sanitize_input t('protocols.steps.published_on',
|
||||
timestamp: l(step.created_at, format: :full),
|
||||
user: h(step.user.full_name)) %></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="panel-collapse collapse" id="step-panel-<%= step.id %>" role="tabpanel">
|
||||
|
@ -34,7 +51,7 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<% if strip_tags(step.description).blank? %>
|
||||
<em><%= t("protocols.steps.no_description") %></em>
|
||||
<em><%= t('protocols.steps.no_description') %></em>
|
||||
<% else %>
|
||||
<div class="ql-editor">
|
||||
<%= custom_auto_link(step.description, false) %>
|
||||
|
@ -86,10 +103,7 @@
|
|||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></p>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to download_asset_path(asset), data: {no_turbolink: true} do %>
|
||||
<p><%= truncate(asset.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></p>
|
||||
<% end %>
|
||||
<%= render partial: 'steps/wopi_controlls.html.erb', locals: { asset: asset } %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
|
@ -129,9 +143,14 @@
|
|||
<div class="checkbox" <%= @protocol.in_module? ? "data-action=check-item" : "" %>>
|
||||
<label>
|
||||
<% if @protocol.in_module? %>
|
||||
<input type="checkbox" value="" data-link-url="<%=checklistitem_state_step_path(step) %>" data-id="<%= checklist_item.id %>" <%= "checked" if checklist_item.checked? %> />
|
||||
<input type="checkbox"
|
||||
value=""
|
||||
data-link-url="<%=checklistitem_state_step_path(step) %>"
|
||||
data-id="<%= checklist_item.id %>" <%= "checked" if checklist_item.checked? %> />
|
||||
<% else %>
|
||||
<input type="checkbox" value="" disabled="disabled" />
|
||||
<input type="checkbox"
|
||||
value=""
|
||||
disabled="disabled" />
|
||||
<% end %>
|
||||
<%= custom_auto_link(checklist_item.text) %>
|
||||
</label>
|
||||
|
@ -144,11 +163,15 @@
|
|||
|
||||
<% if @protocol.in_module? %>
|
||||
<% if !step.completed? and can_complete_step_in_protocol(@protocol) %>
|
||||
<div data-action="complete-step" class="pull-right" data-link-url="<%= toggle_step_state_step_path(step)%>">
|
||||
<div data-action="complete-step"
|
||||
class="pull-right"
|
||||
data-link-url="<%= toggle_step_state_step_path(step)%>">
|
||||
<button class="btn btn-primary"><%= t("protocols.steps.options.complete_title") %></button>
|
||||
</div>
|
||||
<% elsif step.completed? and can_uncomplete_step_in_protocol(@protocol) %>
|
||||
<div data-action="uncomplete-step" class="pull-right" data-link-url="<%= toggle_step_state_step_path(step)%>">
|
||||
<div data-action="uncomplete-step"
|
||||
class="pull-right"
|
||||
data-link-url="<%= toggle_step_state_step_path(step)%>">
|
||||
<button class="btn btn-default"><%= t("protocols.steps.options.uncomplete_title") %></button>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
17
app/views/steps/_wopi_controlls.html.erb
Normal file
17
app/views/steps/_wopi_controlls.html.erb
Normal file
|
@ -0,0 +1,17 @@
|
|||
<%= link_to download_asset_path(asset),
|
||||
data: { no_turbolink: true,
|
||||
id: true,
|
||||
status: 'asset-present' } do %>
|
||||
<%= image_tag preview_asset_path(asset) if asset.is_image? %>
|
||||
|
||||
<% if wopi_file?(asset) %>
|
||||
<%= wopi_asset_file_name(asset) %>
|
||||
<% else %>
|
||||
<p><%= truncate(asset.file_file_name,
|
||||
length: Constants::FILENAME_TRUNCATION_LENGTH) %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= wopi_asset_view_button(asset) %>
|
||||
<% if can_edit_step_in_protocol(@protocol) %>
|
||||
<%= wopi_asset_edit_button(asset) %>
|
||||
<% end %>
|
|
@ -31,6 +31,9 @@ module Scinote
|
|||
end
|
||||
|
||||
# Paperclip spoof checking
|
||||
Paperclip.options[:content_type_mappings] = {:csv => "text/plain"}
|
||||
Paperclip.options[:content_type_mappings] = {
|
||||
csv: 'text/plain',
|
||||
wopitest: ['text/plain', 'inode/x-empty']
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -175,10 +175,16 @@ module Paperclip
|
|||
# Types taken from: http://filext.com/faq/office_mime_types.php and
|
||||
# https://www.openoffice.org/framework/documentation/mimetypes/mimetypes.html
|
||||
#
|
||||
# Generic application
|
||||
(Set[content_type, content_types_from_name].subset? Set.new %w(
|
||||
text/rtf
|
||||
application/rtf
|
||||
)) ||
|
||||
# Word processor application
|
||||
(Set[content_type, content_types_from_name].subset? Set.new %w(
|
||||
application/vnd.ms-office
|
||||
application/msword
|
||||
application/msword-template
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.template
|
||||
application/vnd.ms-word.document.macroEnabled.12
|
||||
|
@ -229,6 +235,7 @@ module Paperclip
|
|||
application/vnd.stardivision.impress
|
||||
application/vnd.stardivision.impress-packed
|
||||
application/x-starimpress
|
||||
text/x-gettext-translation-template
|
||||
)) ||
|
||||
# Graphics application
|
||||
(Set[content_type, content_types_from_name].subset? Set.new %w(
|
||||
|
|
|
@ -526,12 +526,16 @@ en:
|
|||
unlink_error: "Failed to unlink protocol."
|
||||
revert_flash: "Protocol was successfully reverted to protocol version."
|
||||
revert_error: "Failed to revert protocol."
|
||||
revert_error_locked: "Failed to revert protocol. One or more files in the protocol are currently being edited."
|
||||
update_parent_flash: "Protocol in repository was successfully updated with the version from the task."
|
||||
update_parent_error: "Failed to update repository version of the protocol."
|
||||
update_parent_error_locked: "Failed to update repository version of the protocol. One or more files in the protocol are currently being edited."
|
||||
update_from_parent_flash: "Version in the repository was successfully updated."
|
||||
update_from_parent_error: "Failed to update the protocol with the version in the repository."
|
||||
update_from_parent_error_locked: "Failed to update the protocol with the version in the repository. One or more files in the protocol are currently being edited."
|
||||
load_from_repository_flash: "Successfully loaded the protocol from the repository."
|
||||
load_from_repository_error: "Failed to load the protocol from the repository."
|
||||
load_from_repository_error_locked: "Failed to load the protocol from the repository. One or more files in the protocol are currently being edited."
|
||||
load_from_repository_modal:
|
||||
title: "Load protocol from repository"
|
||||
text: "Choose the protocol to be loaded to the task."
|
||||
|
@ -563,6 +567,7 @@ en:
|
|||
confirm: "Copy to repository"
|
||||
load_from_file_flash: "Successfully loaded the protocol from the file."
|
||||
load_from_file_error: "Failed to load the protocol from file."
|
||||
load_from_file_error_locked: "Failed to load the protocol from file. One or more files are currently being edited."
|
||||
load_from_file_size_error: "Failed to load the protocol from file. Limit is %{size}Mb."
|
||||
results:
|
||||
head_title: "%{project} | %{module} | Results"
|
||||
|
@ -787,14 +792,18 @@ en:
|
|||
title: "Edit result from task %{module}"
|
||||
uploaded_asset: "Uploaded file"
|
||||
update: "Update file result"
|
||||
locked_file_error: 'This file is being edited by someone else.'
|
||||
create:
|
||||
success_flash: "Successfully added file result to task <strong>%{module}</strong>"
|
||||
update:
|
||||
success_flash: "Successfully updated file result in task <strong>%{module}</strong>"
|
||||
archive:
|
||||
success_flash: "Successfully archived file result in task <strong>%{module}</strong>"
|
||||
error_flash: "Couldn't archive file result. Someone is editing that file."
|
||||
destroy:
|
||||
success_flash: "File result successfully deleted."
|
||||
wopi_open_file: "Open in %{app}"
|
||||
wopi_edit_file: "Edit in %{app}"
|
||||
|
||||
result_tables:
|
||||
new:
|
||||
|
@ -1025,6 +1034,41 @@ en:
|
|||
delete_step_comment: "<i>%{user}</i> deleted comment on Step %{step} <strong>%{step_name}</strong>."
|
||||
edit_result_comment: "<i>%{user}</i> edited comment on result <strong>%{result}</strong>."
|
||||
delete_result_comment: "<i>%{user}</i> deleted comment on result <strong>%{result}</strong>."
|
||||
start_edit_wopi_file_step: "<i>%{user}</i> started editing File %{file} on Step %{step} <strong>%{step_name}</strong>."
|
||||
start_edit_wopi_file_result: "<i>%{user}</i> started editing File %{file} on Result <strong>%{result}</strong>."
|
||||
unlock_wopi_file_step: "<i>%{user}</i> closed File %{file} for editing on Step %{step} <strong>%{step_name}</strong>."
|
||||
unlock_wopi_file_result: "<i>%{user}</i> started editing File %{file} on Result <strong>%{result}</strong>."
|
||||
|
||||
user_my_modules:
|
||||
new:
|
||||
head_title: "%{project} | %{module} | Add user"
|
||||
title: "Add user to task %{module}"
|
||||
create: "Add user to task"
|
||||
no_users_available: "All users of the current project all already added to this task."
|
||||
assign_user: "Add user"
|
||||
back_button: "Back to task"
|
||||
create:
|
||||
success_flash: "Successfully added user %{user} to task <strong>%{module}</strong>."
|
||||
error_flash: "User %{user} could not be added to task <strong>%{module}</strong>."
|
||||
destroy:
|
||||
success_flash: "Successfully removed user %{user} from task <strong>%{module}</strong>."
|
||||
error_flash: "User %{user} could not be removed from task <strong>%{module}</strong>."
|
||||
|
||||
step_comments:
|
||||
new:
|
||||
head_title: "%{project} | %{module} | Add comment to step"
|
||||
title: "Add comment to step %{step}"
|
||||
create: "Add comment"
|
||||
create:
|
||||
success_flash: "Successfully added comment to step <strong>%{step}</strong>."
|
||||
|
||||
result_comments:
|
||||
new:
|
||||
head_title: "%{project} | %{module} | Add comment to result"
|
||||
title: "Add comment to result"
|
||||
create: "Add comment"
|
||||
create:
|
||||
success_flash: "Successfully added comment to result."
|
||||
load_protocol_from_file: "<i>%{user}</i> loaded protocol <strong>%{protocol}</strong> from file."
|
||||
load_protocol_from_repository: "<i>%{user}</i> loaded protocol <strong>%{protocol}</strong> from repository."
|
||||
revert_protocol: "<i>%{user}</i> reverted protocol <strong>%{protocol}</strong> from repository."
|
||||
|
@ -1379,6 +1423,7 @@ en:
|
|||
destroy:
|
||||
confirm: "Are you sure you want to delete step %{step}?"
|
||||
success_flash: "Step %{step} successfully deleted."
|
||||
error_flash: "Step %{step} couldn't be deleted. One or more files are locked."
|
||||
edit:
|
||||
head_title: "Edit protocol"
|
||||
no_keywords: "No keywords"
|
||||
|
@ -1475,6 +1520,11 @@ en:
|
|||
unassign_user_from_team: "<i>%{unassigned_user}</i> was removed from team <strong>%{team}</strong> by <i>%{unassigned_by_user}</i>."
|
||||
task_completed: "%{user} completed task %{module}. %{date} | Project: %{project} | Experiment: %{experiment}"
|
||||
|
||||
assets:
|
||||
head_title:
|
||||
edit: "sciNote | %{file_name} | Edit"
|
||||
view: "sciNote | %{file_name} | View"
|
||||
|
||||
atwho:
|
||||
no_results: "No results found"
|
||||
users:
|
||||
|
@ -1489,6 +1539,7 @@ en:
|
|||
archived: "(archived)"
|
||||
deleted: "(deleted)"
|
||||
popover_html: "<span class='silver'>Team:</span> %{team} <br> <span class='silver'>Role:</span> %{role} <br> <span class='silver'>Joined:</span> %{time}"
|
||||
|
||||
# This section contains general words that can be used in any parts of
|
||||
# application.
|
||||
|
||||
|
|
754
config/routes.rb
754
config/routes.rb
|
@ -1,394 +1,408 @@
|
|||
Rails.application.routes.draw do
|
||||
devise_for :users, controllers: { registrations: 'users/registrations',
|
||||
sessions: 'users/sessions',
|
||||
invitations: 'users/invitations',
|
||||
confirmations: 'users/confirmations' }
|
||||
require 'subdomain'
|
||||
constraints UserSubdomain do
|
||||
devise_for :users, controllers: { registrations: 'users/registrations',
|
||||
sessions: 'users/sessions',
|
||||
invitations: 'users/invitations',
|
||||
confirmations: 'users/confirmations' }
|
||||
|
||||
root 'projects#index'
|
||||
root 'projects#index'
|
||||
|
||||
# Save sample table state
|
||||
post '/state_save/:team_id/:user_id',
|
||||
to: 'user_samples#save_samples_table_status',
|
||||
as: 'save_samples_table_status',
|
||||
defaults: { format: 'json' }
|
||||
# Save sample table state
|
||||
post '/state_save/:team_id/:user_id',
|
||||
to: 'user_samples#save_samples_table_status',
|
||||
as: 'save_samples_table_status',
|
||||
defaults: { format: 'json' }
|
||||
|
||||
post '/state_load/:team_id/:user_id',
|
||||
to: 'user_samples#load_samples_table_status',
|
||||
as: 'load_samples_table_status',
|
||||
defaults: { format: 'json' }
|
||||
post '/state_load/:team_id/:user_id',
|
||||
to: 'user_samples#load_samples_table_status',
|
||||
as: 'load_samples_table_status',
|
||||
defaults: { format: 'json' }
|
||||
|
||||
resources :activities, only: [:index]
|
||||
resources :activities, only: [:index]
|
||||
|
||||
get 'forbidden', to: 'application#forbidden', as: 'forbidden'
|
||||
get 'not_found', to: 'application#not_found', as: 'not_found'
|
||||
get 'forbidden', to: 'application#forbidden', as: 'forbidden'
|
||||
get 'not_found', to: 'application#not_found', as: 'not_found'
|
||||
|
||||
# Settings
|
||||
get 'users/settings/account/preferences',
|
||||
to: 'users/settings/account/preferences#index',
|
||||
as: 'preferences'
|
||||
put 'users/settings/account/preferences',
|
||||
to: 'users/settings/account/preferences#update',
|
||||
as: 'update_preferences'
|
||||
get 'users/settings/account/preferences/tutorial',
|
||||
to: 'users/settings/account/preferences#tutorial',
|
||||
as: 'tutorial'
|
||||
post 'users/settings/account/preferences/reset_tutorial/',
|
||||
to: 'users/settings/account/preferences#reset_tutorial',
|
||||
as: 'reset_tutorial'
|
||||
post 'users/settings/account/preferences/notifications_settings',
|
||||
to: 'users/settings/account/preferences#notifications_settings',
|
||||
as: 'notifications_settings',
|
||||
defaults: { format: 'json' }
|
||||
# Settings
|
||||
get 'users/settings/account/preferences',
|
||||
to: 'users/settings/account/preferences#index',
|
||||
as: 'preferences'
|
||||
put 'users/settings/account/preferences',
|
||||
to: 'users/settings/account/preferences#update',
|
||||
as: 'update_preferences'
|
||||
get 'users/settings/account/preferences/tutorial',
|
||||
to: 'users/settings/account/preferences#tutorial',
|
||||
as: 'tutorial'
|
||||
post 'users/settings/account/preferences/reset_tutorial/',
|
||||
to: 'users/settings/account/preferences#reset_tutorial',
|
||||
as: 'reset_tutorial'
|
||||
post 'users/settings/account/preferences/notifications_settings',
|
||||
to: 'users/settings/account/preferences#notifications_settings',
|
||||
as: 'notifications_settings',
|
||||
defaults: { format: 'json' }
|
||||
|
||||
# Change user's current team
|
||||
post 'users/settings/user_current_team',
|
||||
to: 'users/settings#user_current_team',
|
||||
as: 'user_current_team'
|
||||
# Change user's current team
|
||||
post 'users/settings/user_current_team',
|
||||
to: 'users/settings#user_current_team',
|
||||
as: 'user_current_team'
|
||||
|
||||
get 'users/settings/teams',
|
||||
to: 'users/settings/teams#index',
|
||||
as: 'teams'
|
||||
post 'users/settings/teams/datatable',
|
||||
to: 'users/settings/teams#datatable',
|
||||
as: 'teams_datatable'
|
||||
get 'users/settings/teams/new',
|
||||
to: 'users/settings/teams#new',
|
||||
as: 'new_team'
|
||||
post 'users/settings/teams',
|
||||
to: 'users/settings/teams#create',
|
||||
as: 'create_team'
|
||||
get 'users/settings/teams/:id',
|
||||
to: 'users/settings/teams#show',
|
||||
as: 'team'
|
||||
post 'users/settings/teams/:id/users_datatable',
|
||||
to: 'users/settings/teams#users_datatable',
|
||||
as: 'team_users_datatable'
|
||||
get 'users/settings/teams/:id/name_html',
|
||||
to: 'users/settings/teams#name_html',
|
||||
as: 'team_name'
|
||||
get 'users/settings/teams/:id/description_html',
|
||||
to: 'users/settings/teams#description_html',
|
||||
as: 'team_description'
|
||||
put 'users/settings/teams/:id',
|
||||
to: 'users/settings/teams#update',
|
||||
as: 'update_team'
|
||||
delete 'users/settings/teams/:id',
|
||||
to: 'users/settings/teams#destroy',
|
||||
as: 'destroy_team'
|
||||
get 'users/settings/teams',
|
||||
to: 'users/settings/teams#index',
|
||||
as: 'teams'
|
||||
post 'users/settings/teams/datatable',
|
||||
to: 'users/settings/teams#datatable',
|
||||
as: 'teams_datatable'
|
||||
get 'users/settings/teams/new',
|
||||
to: 'users/settings/teams#new',
|
||||
as: 'new_team'
|
||||
post 'users/settings/teams',
|
||||
to: 'users/settings/teams#create',
|
||||
as: 'create_team'
|
||||
get 'users/settings/teams/:id',
|
||||
to: 'users/settings/teams#show',
|
||||
as: 'team'
|
||||
post 'users/settings/teams/:id/users_datatable',
|
||||
to: 'users/settings/teams#users_datatable',
|
||||
as: 'team_users_datatable'
|
||||
get 'users/settings/teams/:id/name_html',
|
||||
to: 'users/settings/teams#name_html',
|
||||
as: 'team_name'
|
||||
get 'users/settings/teams/:id/description_html',
|
||||
to: 'users/settings/teams#description_html',
|
||||
as: 'team_description'
|
||||
put 'users/settings/teams/:id',
|
||||
to: 'users/settings/teams#update',
|
||||
as: 'update_team'
|
||||
delete 'users/settings/teams/:id',
|
||||
to: 'users/settings/teams#destroy',
|
||||
as: 'destroy_team'
|
||||
|
||||
put 'users/settings/user_teams/:id',
|
||||
to: 'users/settings/user_teams#update',
|
||||
as: 'update_user_team'
|
||||
get 'users/settings/user_teams/:id/leave_html',
|
||||
to: 'users/settings/user_teams#leave_html',
|
||||
as: 'leave_user_team_html'
|
||||
get 'users/settings/user_teams/:id/destroy_html',
|
||||
to: 'users/settings/user_teams#destroy_html',
|
||||
as: 'destroy_user_team_html'
|
||||
delete 'users/settings/user_teams/:id',
|
||||
to: 'users/settings/user_teams#destroy',
|
||||
as: 'destroy_user_team'
|
||||
put 'users/settings/user_teams/:id',
|
||||
to: 'users/settings/user_teams#update',
|
||||
as: 'update_user_team'
|
||||
get 'users/settings/user_teams/:id/leave_html',
|
||||
to: 'users/settings/user_teams#leave_html',
|
||||
as: 'leave_user_team_html'
|
||||
get 'users/settings/user_teams/:id/destroy_html',
|
||||
to: 'users/settings/user_teams#destroy_html',
|
||||
as: 'destroy_user_team_html'
|
||||
delete 'users/settings/user_teams/:id',
|
||||
to: 'users/settings/user_teams#destroy',
|
||||
as: 'destroy_user_team'
|
||||
|
||||
# Invite users
|
||||
devise_scope :user do
|
||||
post '/invite',
|
||||
to: 'users/invitations#invite_users',
|
||||
as: 'invite_users'
|
||||
end
|
||||
|
||||
# Notifications
|
||||
get 'users/:id/recent_notifications',
|
||||
to: 'user_notifications#recent_notifications',
|
||||
as: 'recent_notifications',
|
||||
defaults: { format: 'json' }
|
||||
|
||||
get 'users/:id/unseen_notification',
|
||||
to: 'user_notifications#unseen_notification',
|
||||
as: 'unseen_notification',
|
||||
defaults: { format: 'json' }
|
||||
|
||||
get 'users/notifications',
|
||||
to: 'user_notifications#index',
|
||||
as: 'notifications'
|
||||
|
||||
resources :teams do
|
||||
resources :samples, only: [:new, :create]
|
||||
resources :sample_types, except: [:show, :new] do
|
||||
get 'sample_type_element', to: 'sample_types#sample_type_element'
|
||||
get 'destroy_confirmation', to: 'sample_types#destroy_confirmation'
|
||||
# Invite users
|
||||
devise_scope :user do
|
||||
post '/invite',
|
||||
to: 'users/invitations#invite_users',
|
||||
as: 'invite_users'
|
||||
end
|
||||
resources :sample_groups, except: [:show, :new] do
|
||||
get 'sample_group_element', to: 'sample_groups#sample_group_element'
|
||||
get 'destroy_confirmation', to: 'sample_groups#destroy_confirmation'
|
||||
end
|
||||
resources :custom_fields, only: [:create, :update, :destroy] do
|
||||
get 'destroy_html'
|
||||
end
|
||||
member do
|
||||
post 'parse_sheet'
|
||||
post 'import_samples'
|
||||
post 'export_samples'
|
||||
# Used for atwho (smart annotations)
|
||||
get 'atwho_users', to: 'at_who#users'
|
||||
get 'atwho_samples', to: 'at_who#samples'
|
||||
get 'atwho_projects', to: 'at_who#projects'
|
||||
get 'atwho_experiments', to: 'at_who#experiments'
|
||||
get 'atwho_my_modules', to: 'at_who#my_modules'
|
||||
get 'atwho_menu_items', to: 'at_who#menu_items'
|
||||
end
|
||||
match '*path',
|
||||
to: 'teams#routing_error',
|
||||
via: [:get, :post, :put, :patch]
|
||||
end
|
||||
|
||||
get 'projects/archive', to: 'projects#archive', as: 'projects_archive'
|
||||
# Notifications
|
||||
get 'users/:id/recent_notifications',
|
||||
to: 'user_notifications#recent_notifications',
|
||||
as: 'recent_notifications',
|
||||
defaults: { format: 'json' }
|
||||
|
||||
resources :projects, except: [:new, :destroy] do
|
||||
resources :user_projects, path: '/users',
|
||||
only: [:create, :index, :update, :destroy]
|
||||
resources :project_comments,
|
||||
path: '/comments',
|
||||
only: [:create, :index, :edit, :update, :destroy]
|
||||
# Activities popup (JSON) for individual project in projects index,
|
||||
# as well as all activities page for single project (HTML)
|
||||
resources :project_activities, path: '/activities', only: [:index]
|
||||
resources :tags, only: [:create, :update, :destroy]
|
||||
resources :reports,
|
||||
path: '/reports',
|
||||
only: [:index, :new, :create, :edit, :update] do
|
||||
collection do
|
||||
# The posts following here should in theory be gets,
|
||||
# but are posts because of parameters payload
|
||||
post 'generate', to: 'reports#generate'
|
||||
get 'new/', to: 'reports#new'
|
||||
get 'new/project_contents_modal',
|
||||
to: 'reports#project_contents_modal',
|
||||
as: :project_contents_modal
|
||||
post 'new/project_contents',
|
||||
to: 'reports#project_contents',
|
||||
as: :project_contents
|
||||
get 'new/experiment_contents_modal',
|
||||
to: 'reports#experiment_contents_modal',
|
||||
as: :experiment_contents_modal
|
||||
post 'new/experiment_contents',
|
||||
to: 'reports#experiment_contents',
|
||||
as: :experiment_contents
|
||||
get 'new/module_contents_modal',
|
||||
to: 'reports#module_contents_modal',
|
||||
as: :module_contents_modal
|
||||
post 'new/module_contents',
|
||||
to: 'reports#module_contents',
|
||||
as: :module_contents
|
||||
get 'new/step_contents_modal',
|
||||
to: 'reports#step_contents_modal',
|
||||
as: :step_contents_modal
|
||||
post 'new/step_contents',
|
||||
to: 'reports#step_contents',
|
||||
as: :step_contents
|
||||
get 'new/result_contents_modal',
|
||||
to: 'reports#result_contents_modal',
|
||||
as: :result_contents_modal
|
||||
post 'new/result_contents',
|
||||
to: 'reports#result_contents',
|
||||
as: :result_contents
|
||||
post '_save',
|
||||
to: 'reports#save_modal',
|
||||
as: :save_modal
|
||||
post 'destroy', as: :destroy # Destroy multiple entries at once
|
||||
get 'users/:id/unseen_notification',
|
||||
to: 'user_notifications#unseen_notification',
|
||||
as: 'unseen_notification',
|
||||
defaults: { format: 'json' }
|
||||
|
||||
get 'users/notifications',
|
||||
to: 'user_notifications#index',
|
||||
as: 'notifications'
|
||||
|
||||
resources :teams do
|
||||
resources :samples, only: [:new, :create]
|
||||
resources :sample_types, except: [:show, :new] do
|
||||
get 'sample_type_element', to: 'sample_types#sample_type_element'
|
||||
get 'destroy_confirmation', to: 'sample_types#destroy_confirmation'
|
||||
end
|
||||
resources :sample_groups, except: [:show, :new] do
|
||||
get 'sample_group_element', to: 'sample_groups#sample_group_element'
|
||||
get 'destroy_confirmation', to: 'sample_groups#destroy_confirmation'
|
||||
end
|
||||
resources :custom_fields, only: [:create, :update, :destroy] do
|
||||
get 'destroy_html'
|
||||
end
|
||||
member do
|
||||
post 'parse_sheet'
|
||||
post 'import_samples'
|
||||
post 'export_samples'
|
||||
# Used for atwho (smart annotations)
|
||||
get 'atwho_users', to: 'at_who#users'
|
||||
get 'atwho_samples', to: 'at_who#samples'
|
||||
get 'atwho_projects', to: 'at_who#projects'
|
||||
get 'atwho_experiments', to: 'at_who#experiments'
|
||||
get 'atwho_my_modules', to: 'at_who#my_modules'
|
||||
get 'atwho_menu_items', to: 'at_who#menu_items'
|
||||
end
|
||||
match '*path',
|
||||
to: 'teams#routing_error',
|
||||
via: [:get, :post, :put, :patch]
|
||||
end
|
||||
|
||||
get 'projects/archive', to: 'projects#archive', as: 'projects_archive'
|
||||
|
||||
resources :projects, except: [:new, :destroy] do
|
||||
resources :user_projects, path: '/users',
|
||||
only: [:create, :index, :update, :destroy]
|
||||
resources :project_comments,
|
||||
path: '/comments',
|
||||
only: [:create, :index, :edit, :update, :destroy]
|
||||
# Activities popup (JSON) for individual project in projects index,
|
||||
# as well as all activities page for single project (HTML)
|
||||
resources :project_activities, path: '/activities', only: [:index]
|
||||
resources :tags, only: [:create, :update, :destroy]
|
||||
resources :reports,
|
||||
path: '/reports',
|
||||
only: [:index, :new, :create, :edit, :update] do
|
||||
collection do
|
||||
# The posts following here should in theory be gets,
|
||||
# but are posts because of parameters payload
|
||||
post 'generate', to: 'reports#generate'
|
||||
get 'new/', to: 'reports#new'
|
||||
get 'new/project_contents_modal',
|
||||
to: 'reports#project_contents_modal',
|
||||
as: :project_contents_modal
|
||||
post 'new/project_contents',
|
||||
to: 'reports#project_contents',
|
||||
as: :project_contents
|
||||
get 'new/experiment_contents_modal',
|
||||
to: 'reports#experiment_contents_modal',
|
||||
as: :experiment_contents_modal
|
||||
post 'new/experiment_contents',
|
||||
to: 'reports#experiment_contents',
|
||||
as: :experiment_contents
|
||||
get 'new/module_contents_modal',
|
||||
to: 'reports#module_contents_modal',
|
||||
as: :module_contents_modal
|
||||
post 'new/module_contents',
|
||||
to: 'reports#module_contents',
|
||||
as: :module_contents
|
||||
get 'new/step_contents_modal',
|
||||
to: 'reports#step_contents_modal',
|
||||
as: :step_contents_modal
|
||||
post 'new/step_contents',
|
||||
to: 'reports#step_contents',
|
||||
as: :step_contents
|
||||
get 'new/result_contents_modal',
|
||||
to: 'reports#result_contents_modal',
|
||||
as: :result_contents_modal
|
||||
post 'new/result_contents',
|
||||
to: 'reports#result_contents',
|
||||
as: :result_contents
|
||||
post '_save',
|
||||
to: 'reports#save_modal',
|
||||
as: :save_modal
|
||||
post 'destroy', as: :destroy # Destroy multiple entries at once
|
||||
end
|
||||
end
|
||||
resources :experiments,
|
||||
only: [:new, :create, :edit, :update],
|
||||
defaults: { format: 'json' }
|
||||
member do
|
||||
# Notifications popup for individual project in projects index
|
||||
get 'notifications'
|
||||
get 'samples' # Samples for single project
|
||||
# Renders sample datatable for single project (ajax action)
|
||||
post 'samples_index'
|
||||
get 'experiment_archive' # Experiment archive for single project
|
||||
post :delete_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
ProjectsController::DELETE_SAMPLES
|
||||
),
|
||||
action: :delete_samples
|
||||
end
|
||||
|
||||
# This route is defined outside of member block
|
||||
# to preserve original :project_id parameter in URL.
|
||||
get 'users/edit', to: 'user_projects#index_edit'
|
||||
end
|
||||
|
||||
resources :experiments do
|
||||
member do
|
||||
get 'canvas' # Overview/structure for single experiment
|
||||
# AJAX-loaded canvas edit mode (from canvas)
|
||||
get 'canvas/edit', to: 'canvas#edit'
|
||||
get 'canvas/full_zoom', to: 'canvas#full_zoom' # AJAX-loaded canvas zoom
|
||||
# AJAX-loaded canvas zoom
|
||||
get 'canvas/medium_zoom', to: 'canvas#medium_zoom'
|
||||
get 'canvas/small_zoom', to: 'canvas#small_zoom' # AJAX-loaded canvas zoom
|
||||
post 'canvas', to: 'canvas#update' # Save updated canvas action
|
||||
get 'module_archive' # Module archive for single experiment
|
||||
get 'archive' # archive experiment
|
||||
get 'clone_modal' # return modal with clone options
|
||||
post 'clone' # clone experiment
|
||||
get 'move_modal' # return modal with move options
|
||||
post 'move' # move experiment
|
||||
get 'samples' # Samples for single project
|
||||
get 'updated_img' # Checks if the workflow image is updated
|
||||
get 'fetch_workflow_img' # Get udated workflow img
|
||||
# Renders sample datatable for single project (ajax action)
|
||||
post 'samples_index'
|
||||
post :delete_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
ExperimentsController::DELETE_SAMPLES
|
||||
),
|
||||
action: :delete_samples
|
||||
end
|
||||
end
|
||||
resources :experiments,
|
||||
only: [:new, :create, :edit, :update],
|
||||
defaults: { format: 'json' }
|
||||
member do
|
||||
# Notifications popup for individual project in projects index
|
||||
get 'notifications'
|
||||
get 'samples' # Samples for single project
|
||||
# Renders sample datatable for single project (ajax action)
|
||||
post 'samples_index'
|
||||
get 'experiment_archive' # Experiment archive for single project
|
||||
post :delete_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
ProjectsController::DELETE_SAMPLES
|
||||
),
|
||||
action: :delete_samples
|
||||
|
||||
# Show action is a popup (JSON) for individual module in full-zoom canvas,
|
||||
# as well as 'module info' page for single module (HTML)
|
||||
resources :my_modules, path: '/modules', only: [:show, :update] do
|
||||
resources :my_module_tags, path: '/tags', only: [:index, :create, :destroy]
|
||||
resources :user_my_modules, path: '/users',
|
||||
only: [:index, :create, :destroy]
|
||||
resources :my_module_comments,
|
||||
path: '/comments',
|
||||
only: [:index, :create, :edit, :update, :destroy]
|
||||
resources :sample_my_modules, path: '/samples_index', only: [:index]
|
||||
resources :result_texts, only: [:new, :create]
|
||||
resources :result_assets, only: [:new, :create]
|
||||
resources :result_tables, only: [:new, :create]
|
||||
member do
|
||||
# AJAX popup accessed from full-zoom canvas for single module,
|
||||
# as well as full activities view (HTML) for single module
|
||||
get 'description'
|
||||
get 'activities'
|
||||
get 'activities_tab' # Activities in tab view for single module
|
||||
get 'due_date'
|
||||
get 'protocols' # Protocols view for single module
|
||||
get 'results' # Results view for single module
|
||||
get 'samples' # Samples view for single module
|
||||
get 'archive' # Archive view for single module
|
||||
post 'toggle_task_state'
|
||||
# Renders sample datatable for single module (ajax action)
|
||||
post 'samples_index'
|
||||
post :assign_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
MyModulesController::ASSIGN_SAMPLES
|
||||
),
|
||||
action: :assign_samples
|
||||
post :assign_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
MyModulesController::UNASSIGN_SAMPLES
|
||||
),
|
||||
action: :unassign_samples
|
||||
post :assign_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
MyModulesController::DELETE_SAMPLES
|
||||
),
|
||||
action: :delete_samples
|
||||
end
|
||||
|
||||
# Those routes are defined outside of member block
|
||||
# to preserve original id parameters in URL.
|
||||
get 'tags/edit', to: 'my_module_tags#index_edit'
|
||||
get 'users/edit', to: 'user_my_modules#index_edit'
|
||||
end
|
||||
|
||||
# This route is defined outside of member block
|
||||
# to preserve original :project_id parameter in URL.
|
||||
get 'users/edit', to: 'user_projects#index_edit'
|
||||
end
|
||||
resources :steps, only: [:edit, :update, :destroy, :show] do
|
||||
resources :step_comments,
|
||||
path: '/comments',
|
||||
only: [:create, :index, :edit, :update, :destroy]
|
||||
member do
|
||||
post 'checklistitem_state'
|
||||
post 'toggle_step_state'
|
||||
get 'move_down'
|
||||
get 'move_up'
|
||||
end
|
||||
end
|
||||
|
||||
resources :experiments do
|
||||
member do
|
||||
get 'canvas' # Overview/structure for single experiment
|
||||
# AJAX-loaded canvas edit mode (from canvas)
|
||||
get 'canvas/edit', to: 'canvas#edit'
|
||||
get 'canvas/full_zoom', to: 'canvas#full_zoom' # AJAX-loaded canvas zoom
|
||||
# AJAX-loaded canvas zoom
|
||||
get 'canvas/medium_zoom', to: 'canvas#medium_zoom'
|
||||
get 'canvas/small_zoom', to: 'canvas#small_zoom' # AJAX-loaded canvas zoom
|
||||
post 'canvas', to: 'canvas#update' # Save updated canvas action
|
||||
get 'module_archive' # Module archive for single experiment
|
||||
get 'archive' # archive experiment
|
||||
get 'clone_modal' # return modal with clone options
|
||||
post 'clone' # clone experiment
|
||||
get 'move_modal' # return modal with move options
|
||||
post 'move' # move experiment
|
||||
get 'samples' # Samples for single project
|
||||
get 'updated_img' # Checks if the workflow image is updated
|
||||
get 'fetch_workflow_img' # Get udated workflow img
|
||||
# Renders sample datatable for single project (ajax action)
|
||||
post 'samples_index'
|
||||
post :delete_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
ExperimentsController::DELETE_SAMPLES
|
||||
),
|
||||
action: :delete_samples
|
||||
resources :results, only: [:update, :destroy] do
|
||||
resources :result_comments,
|
||||
path: '/comments',
|
||||
only: [:create, :index, :edit, :update, :destroy]
|
||||
end
|
||||
|
||||
resources :samples, only: [:edit, :update, :destroy]
|
||||
get 'samples/:id', to: 'samples#show'
|
||||
|
||||
resources :result_texts, only: [:edit, :update, :destroy]
|
||||
get 'result_texts/:id/download' => 'result_texts#download',
|
||||
as: :result_text_download
|
||||
resources :result_assets, only: [:edit, :update, :destroy]
|
||||
get 'result_assets/:id/download' => 'result_assets#download',
|
||||
as: :result_asset_download
|
||||
resources :result_tables, only: [:edit, :update, :destroy]
|
||||
get 'result_tables/:id/download' => 'result_tables#download',
|
||||
as: :result_table_download
|
||||
|
||||
resources :protocols, only: [:index, :edit, :create] do
|
||||
resources :steps, only: [:new, :create]
|
||||
member do
|
||||
get 'linked_children', to: 'protocols#linked_children'
|
||||
post 'linked_children_datatable',
|
||||
to: 'protocols#linked_children_datatable'
|
||||
get 'preview', to: 'protocols#preview'
|
||||
patch 'metadata', to: 'protocols#update_metadata'
|
||||
patch 'keywords', to: 'protocols#update_keywords'
|
||||
post 'clone', to: 'protocols#clone'
|
||||
get 'unlink_modal', to: 'protocols#unlink_modal'
|
||||
post 'unlink', to: 'protocols#unlink'
|
||||
get 'revert_modal', to: 'protocols#revert_modal'
|
||||
post 'revert', to: 'protocols#revert'
|
||||
get 'update_parent_modal', to: 'protocols#update_parent_modal'
|
||||
post 'update_parent', to: 'protocols#update_parent'
|
||||
get 'update_from_parent_modal', to: 'protocols#update_from_parent_modal'
|
||||
post 'update_from_parent', to: 'protocols#update_from_parent'
|
||||
post 'load_from_repository_datatable',
|
||||
to: 'protocols#load_from_repository_datatable'
|
||||
get 'load_from_repository_modal',
|
||||
to: 'protocols#load_from_repository_modal'
|
||||
post 'load_from_repository', to: 'protocols#load_from_repository'
|
||||
post 'load_from_file', to: 'protocols#load_from_file'
|
||||
get 'copy_to_repository_modal', to: 'protocols#copy_to_repository_modal'
|
||||
post 'copy_to_repository', to: 'protocols#copy_to_repository'
|
||||
get 'protocol_status_bar', to: 'protocols#protocol_status_bar'
|
||||
get 'updated_at_label', to: 'protocols#updated_at_label'
|
||||
get 'edit_name_modal', to: 'protocols#edit_name_modal'
|
||||
get 'edit_keywords_modal', to: 'protocols#edit_keywords_modal'
|
||||
get 'edit_authors_modal', to: 'protocols#edit_authors_modal'
|
||||
get 'edit_description_modal', to: 'protocols#edit_description_modal'
|
||||
end
|
||||
collection do
|
||||
get 'create_new_modal', to: 'protocols#create_new_modal'
|
||||
post 'datatable', to: 'protocols#datatable'
|
||||
post 'make_private', to: 'protocols#make_private'
|
||||
post 'publish', to: 'protocols#publish'
|
||||
post 'archive', to: 'protocols#archive'
|
||||
post 'restore', to: 'protocols#restore'
|
||||
post 'import', to: 'protocols#import'
|
||||
get 'export', to: 'protocols#export'
|
||||
end
|
||||
end
|
||||
|
||||
get 'search' => 'search#index'
|
||||
get 'search/new' => 'search#new', as: :new_search
|
||||
|
||||
# We cannot use 'resources :assets' because assets is a reserved route
|
||||
# in Rails (assets pipeline) and causes funky behavior
|
||||
get 'files/:id/present', to: 'assets#file_present', as: 'file_present_asset'
|
||||
get 'files/:id/large_url',
|
||||
to: 'assets#large_image_url',
|
||||
as: 'large_image_url_asset'
|
||||
get 'files/:id/download', to: 'assets#download', as: 'download_asset'
|
||||
get 'files/:id/preview', to: 'assets#preview', as: 'preview_asset'
|
||||
get 'files/:id/view', to: 'assets#view', as: 'view_asset'
|
||||
get 'files/:id/edit', to: 'assets#edit', as: 'edit_asset'
|
||||
post 'asset_signature' => 'assets#signature'
|
||||
|
||||
devise_scope :user do
|
||||
get 'avatar/:id/:style' => 'users/registrations#avatar', as: 'avatar'
|
||||
post 'avatar_signature' => 'users/registrations#signature'
|
||||
get 'users/auth_token_sign_in' => 'users/sessions#auth_token_create'
|
||||
end
|
||||
end
|
||||
|
||||
# Show action is a popup (JSON) for individual module in full-zoom canvas,
|
||||
# as well as 'module info' page for single module (HTML)
|
||||
resources :my_modules, path: '/modules', only: [:show, :update] do
|
||||
resources :my_module_tags, path: '/tags', only: [:index, :create, :destroy]
|
||||
resources :user_my_modules, path: '/users',
|
||||
only: [:index, :create, :destroy]
|
||||
resources :my_module_comments,
|
||||
path: '/comments',
|
||||
only: [:index, :create, :edit, :update, :destroy]
|
||||
resources :sample_my_modules, path: '/samples_index', only: [:index]
|
||||
resources :result_texts, only: [:new, :create]
|
||||
resources :result_assets, only: [:new, :create]
|
||||
resources :result_tables, only: [:new, :create]
|
||||
member do
|
||||
# AJAX popup accessed from full-zoom canvas for single module,
|
||||
# as well as full activities view (HTML) for single module
|
||||
get 'description'
|
||||
get 'activities'
|
||||
get 'activities_tab' # Activities in tab view for single module
|
||||
get 'due_date'
|
||||
get 'protocols' # Protocols view for single module
|
||||
get 'results' # Results view for single module
|
||||
get 'samples' # Samples view for single module
|
||||
get 'archive' # Archive view for single module
|
||||
post 'toggle_task_state'
|
||||
# Renders sample datatable for single module (ajax action)
|
||||
post 'samples_index'
|
||||
post :assign_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
MyModulesController::ASSIGN_SAMPLES
|
||||
),
|
||||
action: :assign_samples
|
||||
post :assign_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
MyModulesController::UNASSIGN_SAMPLES
|
||||
),
|
||||
action: :unassign_samples
|
||||
post :assign_samples,
|
||||
constraints: CommitParamRouting.new(
|
||||
MyModulesController::DELETE_SAMPLES
|
||||
),
|
||||
action: :delete_samples
|
||||
end
|
||||
|
||||
# Those routes are defined outside of member block
|
||||
# to preserve original id parameters in URL.
|
||||
get 'tags/edit', to: 'my_module_tags#index_edit'
|
||||
get 'users/edit', to: 'user_my_modules#index_edit'
|
||||
end
|
||||
|
||||
resources :steps, only: [:edit, :update, :destroy, :show] do
|
||||
resources :step_comments,
|
||||
path: '/comments',
|
||||
only: [:create, :index, :edit, :update, :destroy]
|
||||
member do
|
||||
post 'checklistitem_state'
|
||||
post 'toggle_step_state'
|
||||
get 'move_down'
|
||||
get 'move_up'
|
||||
end
|
||||
end
|
||||
|
||||
resources :results, only: [:update, :destroy] do
|
||||
resources :result_comments,
|
||||
path: '/comments',
|
||||
only: [:create, :index, :edit, :update, :destroy]
|
||||
end
|
||||
|
||||
resources :samples, only: [:edit, :update, :destroy]
|
||||
get 'samples/:id', to: 'samples#show'
|
||||
|
||||
resources :result_texts, only: [:edit, :update, :destroy]
|
||||
get 'result_texts/:id/download' => 'result_texts#download',
|
||||
as: :result_text_download
|
||||
resources :result_assets, only: [:edit, :update, :destroy]
|
||||
get 'result_assets/:id/download' => 'result_assets#download',
|
||||
as: :result_asset_download
|
||||
resources :result_tables, only: [:edit, :update, :destroy]
|
||||
get 'result_tables/:id/download' => 'result_tables#download',
|
||||
as: :result_table_download
|
||||
|
||||
resources :protocols, only: [:index, :edit, :create] do
|
||||
resources :steps, only: [:new, :create]
|
||||
member do
|
||||
get 'linked_children', to: 'protocols#linked_children'
|
||||
post 'linked_children_datatable',
|
||||
to: 'protocols#linked_children_datatable'
|
||||
get 'preview', to: 'protocols#preview'
|
||||
patch 'metadata', to: 'protocols#update_metadata'
|
||||
patch 'keywords', to: 'protocols#update_keywords'
|
||||
post 'clone', to: 'protocols#clone'
|
||||
get 'unlink_modal', to: 'protocols#unlink_modal'
|
||||
post 'unlink', to: 'protocols#unlink'
|
||||
get 'revert_modal', to: 'protocols#revert_modal'
|
||||
post 'revert', to: 'protocols#revert'
|
||||
get 'update_parent_modal', to: 'protocols#update_parent_modal'
|
||||
post 'update_parent', to: 'protocols#update_parent'
|
||||
get 'update_from_parent_modal', to: 'protocols#update_from_parent_modal'
|
||||
post 'update_from_parent', to: 'protocols#update_from_parent'
|
||||
post 'load_from_repository_datatable',
|
||||
to: 'protocols#load_from_repository_datatable'
|
||||
get 'load_from_repository_modal',
|
||||
to: 'protocols#load_from_repository_modal'
|
||||
post 'load_from_repository', to: 'protocols#load_from_repository'
|
||||
post 'load_from_file', to: 'protocols#load_from_file'
|
||||
get 'copy_to_repository_modal', to: 'protocols#copy_to_repository_modal'
|
||||
post 'copy_to_repository', to: 'protocols#copy_to_repository'
|
||||
get 'protocol_status_bar', to: 'protocols#protocol_status_bar'
|
||||
get 'updated_at_label', to: 'protocols#updated_at_label'
|
||||
get 'edit_name_modal', to: 'protocols#edit_name_modal'
|
||||
get 'edit_keywords_modal', to: 'protocols#edit_keywords_modal'
|
||||
get 'edit_authors_modal', to: 'protocols#edit_authors_modal'
|
||||
get 'edit_description_modal', to: 'protocols#edit_description_modal'
|
||||
end
|
||||
collection do
|
||||
get 'create_new_modal', to: 'protocols#create_new_modal'
|
||||
post 'datatable', to: 'protocols#datatable'
|
||||
post 'make_private', to: 'protocols#make_private'
|
||||
post 'publish', to: 'protocols#publish'
|
||||
post 'archive', to: 'protocols#archive'
|
||||
post 'restore', to: 'protocols#restore'
|
||||
post 'import', to: 'protocols#import'
|
||||
get 'export', to: 'protocols#export'
|
||||
end
|
||||
end
|
||||
|
||||
get 'search' => 'search#index'
|
||||
get 'search/new' => 'search#new', as: :new_search
|
||||
|
||||
# We cannot use 'resources :assets' because assets is a reserved route
|
||||
# in Rails (assets pipeline) and causes funky behavior
|
||||
get 'files/:id/present', to: 'assets#file_present', as: 'file_present_asset'
|
||||
get 'files/:id/large_url',
|
||||
to: 'assets#large_image_url',
|
||||
as: 'large_image_url_asset'
|
||||
get 'files/:id/download', to: 'assets#download', as: 'download_asset'
|
||||
post 'asset_signature' => 'assets#signature'
|
||||
|
||||
devise_scope :user do
|
||||
get 'avatar/:id/:style' => 'users/registrations#avatar', as: 'avatar'
|
||||
post 'avatar_signature' => 'users/registrations#signature'
|
||||
get 'users/auth_token_sign_in' => 'users/sessions#auth_token_create'
|
||||
constraints WopiSubdomain do
|
||||
# Office integration
|
||||
get 'wopi/files/:id/contents', to: 'wopi#file_contents_get_endpoint'
|
||||
post 'wopi/files/:id/contents', to: 'wopi#file_contents_post_endpoint'
|
||||
get 'wopi/files/:id', to: 'wopi#file_get_endpoint', as: 'wopi_rest_endpoint'
|
||||
post 'wopi/files/:id', to: 'wopi#post_file_endpoint'
|
||||
end
|
||||
end
|
||||
|
|
51
db/migrate/20161129111100_add_wopi.rb
Normal file
51
db/migrate/20161129111100_add_wopi.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
class AddWopi < ActiveRecord::Migration
|
||||
def up
|
||||
add_column :assets, :lock, :string, limit: 1024
|
||||
add_column :assets, :lock_ttl, :integer
|
||||
add_column :assets, :version, :integer, default: 1
|
||||
|
||||
create_table :wopi_discoveries do |t|
|
||||
t.integer :expires, null: false
|
||||
t.string :proof_key_mod, null: false
|
||||
t.string :proof_key_exp, null: false
|
||||
t.string :proof_key_old_mod, null: false
|
||||
t.string :proof_key_old_exp, null: false
|
||||
end
|
||||
|
||||
create_table :wopi_apps do |t|
|
||||
t.string :name, null: false
|
||||
t.string :icon, null: false
|
||||
t.integer :wopi_discovery_id, null: false
|
||||
end
|
||||
|
||||
create_table :wopi_actions do |t|
|
||||
t.string :action, null: false
|
||||
t.string :extension, null: false
|
||||
t.string :urlsrc, null: false
|
||||
t.integer :wopi_app_id, null: false
|
||||
end
|
||||
|
||||
create_table :tokens do |t|
|
||||
t.string :token, null: false
|
||||
t.integer :ttl, null: false
|
||||
t.integer :user_id, null: false
|
||||
end
|
||||
|
||||
add_foreign_key :wopi_actions, :wopi_apps, column: :wopi_app_id
|
||||
add_foreign_key :wopi_apps, :wopi_discoveries, column: :wopi_discovery_id
|
||||
add_foreign_key :tokens, :users, column: :user_id
|
||||
|
||||
add_index :wopi_actions, [:extension, :action]
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :assets, :lock
|
||||
remove_column :assets, :lock_ttl
|
||||
remove_column :assets, :version
|
||||
|
||||
drop_table :wopi_actions
|
||||
drop_table :wopi_apps
|
||||
drop_table :wopi_discoveries
|
||||
drop_table :tokens
|
||||
end
|
||||
end
|
120
db/schema.rb
120
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20170124135736) do
|
||||
ActiveRecord::Schema.define(version: 20170306121855) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -46,16 +46,19 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_index "asset_text_data", ["data_vector"], name: "index_asset_text_data_on_data_vector", using: :gin
|
||||
|
||||
create_table "assets", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "file_file_name"
|
||||
t.string "file_content_type"
|
||||
t.integer "file_file_size"
|
||||
t.datetime "file_updated_at"
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.integer "estimated_size", default: 0, null: false
|
||||
t.boolean "file_present", default: false, null: false
|
||||
t.integer "estimated_size", default: 0, null: false
|
||||
t.boolean "file_present", default: false, null: false
|
||||
t.string "lock", limit: 1024
|
||||
t.integer "lock_ttl"
|
||||
t.integer "version", default: 1
|
||||
t.boolean "file_processing"
|
||||
end
|
||||
|
||||
|
@ -97,8 +100,11 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "last_modified_by_id"
|
||||
t.string "type"
|
||||
t.integer "associated_id"
|
||||
end
|
||||
|
||||
add_index "comments", ["associated_id"], name: "index_comments_on_associated_id", using: :btree
|
||||
add_index "comments", ["created_at"], name: "index_comments_on_created_at", using: :btree
|
||||
add_index "comments", ["last_modified_by_id"], name: "index_comments_on_last_modified_by_id", using: :btree
|
||||
add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree
|
||||
|
@ -169,13 +175,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
t.string "message", null: false
|
||||
end
|
||||
|
||||
create_table "my_module_comments", force: :cascade do |t|
|
||||
t.integer "my_module_id", null: false
|
||||
t.integer "comment_id", null: false
|
||||
end
|
||||
|
||||
add_index "my_module_comments", ["my_module_id", "comment_id"], name: "index_my_module_comments_on_my_module_id_and_comment_id", using: :btree
|
||||
|
||||
create_table "my_module_groups", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.datetime "created_at", null: false
|
||||
|
@ -199,24 +198,26 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_index "my_module_tags", ["tag_id"], name: "index_my_module_tags_on_tag_id", using: :btree
|
||||
|
||||
create_table "my_modules", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "name", null: false
|
||||
t.datetime "due_date"
|
||||
t.string "description"
|
||||
t.integer "x", default: 0, null: false
|
||||
t.integer "y", default: 0, null: false
|
||||
t.integer "x", default: 0, null: false
|
||||
t.integer "y", default: 0, null: false
|
||||
t.integer "my_module_group_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "archived", default: false, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "archived", default: false, null: false
|
||||
t.datetime "archived_on"
|
||||
t.integer "created_by_id"
|
||||
t.integer "last_modified_by_id"
|
||||
t.integer "archived_by_id"
|
||||
t.integer "restored_by_id"
|
||||
t.datetime "restored_on"
|
||||
t.integer "nr_of_assigned_samples", default: 0
|
||||
t.integer "workflow_order", default: -1, null: false
|
||||
t.integer "experiment_id", default: 0, null: false
|
||||
t.integer "nr_of_assigned_samples", default: 0
|
||||
t.integer "workflow_order", default: -1, null: false
|
||||
t.integer "experiment_id", default: 0, null: false
|
||||
t.integer "state", limit: 2, default: 0
|
||||
t.datetime "completed_on"
|
||||
end
|
||||
|
||||
add_index "my_modules", ["archived_by_id"], name: "index_my_modules_on_archived_by_id", using: :btree
|
||||
|
@ -238,13 +239,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
|
||||
add_index "notifications", ["created_at"], name: "index_notifications_on_created_at", using: :btree
|
||||
|
||||
create_table "project_comments", force: :cascade do |t|
|
||||
t.integer "project_id", null: false
|
||||
t.integer "comment_id", null: false
|
||||
end
|
||||
|
||||
add_index "project_comments", ["project_id", "comment_id"], name: "index_project_comments_on_project_id_and_comment_id", using: :btree
|
||||
|
||||
create_table "projects", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.integer "visibility", default: 0, null: false
|
||||
|
@ -367,13 +361,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
|
||||
add_index "result_assets", ["result_id", "asset_id"], name: "index_result_assets_on_result_id_and_asset_id", using: :btree
|
||||
|
||||
create_table "result_comments", force: :cascade do |t|
|
||||
t.integer "result_id", null: false
|
||||
t.integer "comment_id", null: false
|
||||
end
|
||||
|
||||
add_index "result_comments", ["result_id", "comment_id"], name: "index_result_comments_on_result_id_and_comment_id", using: :btree
|
||||
|
||||
create_table "result_tables", force: :cascade do |t|
|
||||
t.integer "result_id", null: false
|
||||
t.integer "table_id", null: false
|
||||
|
@ -410,13 +397,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_index "results", ["restored_by_id"], name: "index_results_on_restored_by_id", using: :btree
|
||||
add_index "results", ["user_id"], name: "index_results_on_user_id", using: :btree
|
||||
|
||||
create_table "sample_comments", force: :cascade do |t|
|
||||
t.integer "sample_id", null: false
|
||||
t.integer "comment_id", null: false
|
||||
end
|
||||
|
||||
add_index "sample_comments", ["sample_id", "comment_id"], name: "index_sample_comments_on_sample_id_and_comment_id", using: :btree
|
||||
|
||||
create_table "sample_custom_fields", force: :cascade do |t|
|
||||
t.string "value", null: false
|
||||
t.integer "custom_field_id", null: false
|
||||
|
@ -495,6 +475,13 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_index "samples_tables", ["team_id"], name: "index_samples_tables_on_team_id", using: :btree
|
||||
add_index "samples_tables", ["user_id"], name: "index_samples_tables_on_user_id", using: :btree
|
||||
|
||||
create_table "settings", force: :cascade do |t|
|
||||
t.text "type", null: false
|
||||
t.jsonb "values", default: {}, null: false
|
||||
end
|
||||
|
||||
add_index "settings", ["type"], name: "index_settings_on_type", unique: true, using: :btree
|
||||
|
||||
create_table "step_assets", force: :cascade do |t|
|
||||
t.integer "step_id", null: false
|
||||
t.integer "asset_id", null: false
|
||||
|
@ -502,13 +489,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
|
||||
add_index "step_assets", ["step_id", "asset_id"], name: "index_step_assets_on_step_id_and_asset_id", using: :btree
|
||||
|
||||
create_table "step_comments", force: :cascade do |t|
|
||||
t.integer "step_id", null: false
|
||||
t.integer "comment_id", null: false
|
||||
end
|
||||
|
||||
add_index "step_comments", ["step_id", "comment_id"], name: "index_step_comments_on_step_id_and_comment_id", using: :btree
|
||||
|
||||
create_table "step_tables", force: :cascade do |t|
|
||||
t.integer "step_id", null: false
|
||||
t.integer "table_id", null: false
|
||||
|
@ -590,6 +570,12 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
t.datetime "file_updated_at"
|
||||
end
|
||||
|
||||
create_table "tokens", force: :cascade do |t|
|
||||
t.string "token", null: false
|
||||
t.integer "ttl", null: false
|
||||
t.integer "user_id", null: false
|
||||
end
|
||||
|
||||
create_table "user_my_modules", force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.integer "my_module_id", null: false
|
||||
|
@ -691,6 +677,29 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_index "users", ["invited_by_id"], name: "index_users_on_invited_by_id", using: :btree
|
||||
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
|
||||
|
||||
create_table "wopi_actions", force: :cascade do |t|
|
||||
t.string "action", null: false
|
||||
t.string "extension", null: false
|
||||
t.string "urlsrc", null: false
|
||||
t.integer "wopi_app_id", null: false
|
||||
end
|
||||
|
||||
add_index "wopi_actions", ["extension", "action"], name: "index_wopi_actions_on_extension_and_action", using: :btree
|
||||
|
||||
create_table "wopi_apps", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "icon", null: false
|
||||
t.integer "wopi_discovery_id", null: false
|
||||
end
|
||||
|
||||
create_table "wopi_discoveries", force: :cascade do |t|
|
||||
t.integer "expires", null: false
|
||||
t.string "proof_key_mod", null: false
|
||||
t.string "proof_key_exp", null: false
|
||||
t.string "proof_key_old_mod", null: false
|
||||
t.string "proof_key_old_exp", null: false
|
||||
end
|
||||
|
||||
add_foreign_key "activities", "my_modules"
|
||||
add_foreign_key "activities", "projects"
|
||||
add_foreign_key "activities", "users"
|
||||
|
@ -715,8 +724,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_foreign_key "experiments", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "experiments", "users", column: "restored_by_id"
|
||||
add_foreign_key "logs", "teams"
|
||||
add_foreign_key "my_module_comments", "comments"
|
||||
add_foreign_key "my_module_comments", "my_modules"
|
||||
add_foreign_key "my_module_groups", "experiments"
|
||||
add_foreign_key "my_module_groups", "users", column: "created_by_id"
|
||||
add_foreign_key "my_module_tags", "users", column: "created_by_id"
|
||||
|
@ -727,8 +734,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_foreign_key "my_modules", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "my_modules", "users", column: "restored_by_id"
|
||||
add_foreign_key "notifications", "users", column: "generator_user_id"
|
||||
add_foreign_key "project_comments", "comments"
|
||||
add_foreign_key "project_comments", "projects"
|
||||
add_foreign_key "projects", "teams"
|
||||
add_foreign_key "projects", "users", column: "archived_by_id"
|
||||
add_foreign_key "projects", "users", column: "created_by_id"
|
||||
|
@ -757,8 +762,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_foreign_key "reports", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "result_assets", "assets"
|
||||
add_foreign_key "result_assets", "results"
|
||||
add_foreign_key "result_comments", "comments"
|
||||
add_foreign_key "result_comments", "results"
|
||||
add_foreign_key "result_tables", "results"
|
||||
add_foreign_key "result_tables", "tables"
|
||||
add_foreign_key "result_texts", "results"
|
||||
|
@ -767,8 +770,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_foreign_key "results", "users", column: "archived_by_id"
|
||||
add_foreign_key "results", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "results", "users", column: "restored_by_id"
|
||||
add_foreign_key "sample_comments", "comments"
|
||||
add_foreign_key "sample_comments", "samples"
|
||||
add_foreign_key "sample_custom_fields", "custom_fields"
|
||||
add_foreign_key "sample_custom_fields", "samples"
|
||||
add_foreign_key "sample_groups", "teams"
|
||||
|
@ -787,8 +788,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_foreign_key "samples", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "step_assets", "assets"
|
||||
add_foreign_key "step_assets", "steps"
|
||||
add_foreign_key "step_comments", "comments"
|
||||
add_foreign_key "step_comments", "steps"
|
||||
add_foreign_key "step_tables", "steps"
|
||||
add_foreign_key "step_tables", "tables"
|
||||
add_foreign_key "steps", "protocols"
|
||||
|
@ -801,6 +800,7 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_foreign_key "tags", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "teams", "users", column: "created_by_id"
|
||||
add_foreign_key "teams", "users", column: "last_modified_by_id"
|
||||
add_foreign_key "tokens", "users"
|
||||
add_foreign_key "user_my_modules", "my_modules"
|
||||
add_foreign_key "user_my_modules", "users"
|
||||
add_foreign_key "user_my_modules", "users", column: "assigned_by_id"
|
||||
|
@ -813,4 +813,6 @@ ActiveRecord::Schema.define(version: 20170124135736) do
|
|||
add_foreign_key "user_teams", "users"
|
||||
add_foreign_key "user_teams", "users", column: "assigned_by_id"
|
||||
add_foreign_key "users", "teams", column: "current_team_id"
|
||||
add_foreign_key "wopi_actions", "wopi_apps"
|
||||
add_foreign_key "wopi_apps", "wopi_discoveries"
|
||||
end
|
||||
|
|
BIN
public/images/office/Excel-color_16x16x32.png
Normal file
BIN
public/images/office/Excel-color_16x16x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/office/Excel-xlsx_20x20x32.png
Normal file
BIN
public/images/office/Excel-xlsx_20x20x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/office/PowerPoint-Color_16x16x32.png
Normal file
BIN
public/images/office/PowerPoint-Color_16x16x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/office/PowerPoint-pptx_20x20x32.png
Normal file
BIN
public/images/office/PowerPoint-pptx_20x20x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/office/Word-color_16x16x32.png
Normal file
BIN
public/images/office/Word-color_16x16x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
public/images/office/Word-docx_20x20x32.png
Normal file
BIN
public/images/office/Word-docx_20x20x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
6
test/fixtures/wopi_discoveries.yml
vendored
Normal file
6
test/fixtures/wopi_discoveries.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
first:
|
||||
expires: <%= Time.now + 1.days %>
|
||||
proof_key_mod: '0HOWUPFFgmSYHbLZZzdWO/HUOr8YNfx5NAl7GUytooHZ7B9QxQKTJpj0NIJ4XEskQW8e4dLzRrPbNOOJ+KpWHttXz8HoQXkkZV/gYNxaNHJ8/pRXGMZzfVM5vchhx/2C7ULPTrpBsSpmfWQ6ShaVoQzfThFUd0MsBvIN7HVtqzPx9jbSV04wAqyNjcro7F3iu9w7AEsMejHbFlWoN+J05dP5ixryF7+2U5RVmjMt7/dYUdCoiXvCMt2CaVr0XEG6udHU4iDKVKZjmUBc7cTWRzhqEL7lZ1yQfylp38Nd2xxVJ0sSU7OkC1bBDlePcYGaF3JjJgsmp/H5BNnlW9gSxQ=='
|
||||
proof_key_exp: 'AQAB'
|
||||
proof_key_old_mod: 'u/ppb/da4jeKQ+XzKr69VJTqR7wgQp2jzDIaEPQVzfwod+pc1zvO7cwjNgfzF/KQGkltoOi9KdtMzR0qmX8C5wZI6wGpS8S4pTFAZPhXg5w4EpyR8fAagrnlOgaVLs0oX5UuBqKndCQyM7Vj5nFd+r53giS0ch7zDW0uB1G+ZWqTZ1TwbtV6dmlpVuJYeIPonOJgo2iuh455KuS2gvxZKOKR27Uq7W949oM8sqRjvfaVf4xDmyor++98XX0zadnf4pMWfPr3XE+bCXtB9jIPAxxMrALf5ncNRhnx0Wyf8zfM7Rfq+omp/HxCgusF5MC2/Ffnn7me/628zzioAMy5pQ=='
|
||||
proof_key_old_exp: 'AQAB'
|
218
test/models/wopi_discovery_test.rb
Normal file
218
test/models/wopi_discovery_test.rb
Normal file
|
@ -0,0 +1,218 @@
|
|||
class WopiDiscoveryTest < ActiveSupport::TestCase
|
||||
# These tests are taken from WOPI official documentation
|
||||
# https://github.com/Microsoft/Office-Online-Test-Tools-and-Documentation/
|
||||
# blob/master/samples/python/proof_keys/tests.py
|
||||
def setup
|
||||
@discovery = wopi_discoveries(:first)
|
||||
end
|
||||
|
||||
test 'Verify X-WOPI-Proof with current discovery proof key1.' do
|
||||
assert @discovery.verify_proof(
|
||||
'yZhdN1qgywcOQWhyEMVpB6NE3pvBksvcLXsrFKXNtBeDTPW%2fu62g2t%' \
|
||||
'2fOCWSlb3jUGaz1zc%2fzOzbNgAredLdhQI1Q7sPPqUv2owO78olmN74D' \
|
||||
'V%2fv52OZIkBG%2b8jqjwmUobcjXVIC1BG9g%2fynMN0itZklL2x27Z2i' \
|
||||
'mCF6xELcQUuGdkoXBj%2bI%2bTlKM',
|
||||
635655897610773532,
|
||||
'IflL8OWCOCmws5qnDD5kYMraMGI3o+T+hojoDREbjZSkxbbx7XIS1Av85' \
|
||||
'lohPKjyksocpeVwqEYm9nVWfnq05uhDNGp2MsNyhPO9unZ6w25Rjs1hDF' \
|
||||
'M0dmvYx8wlQBNZ/CFPaz3inCMaaP4PtU85YepaDccAjNc1gikdy3kSMeG' \
|
||||
'1XZuaDixHvMKzF/60DMfLMBIu5xP4Nt8i8Gi2oZs4REuxi6yxOv2vQJQ5' \
|
||||
'+8Wu2Olm8qZvT4FEIQT9oZAXebn/CxyvyQv+RVpoU2gb4BreXAdfKthWF' \
|
||||
'67GpJyhr+ibEVDoIIolUvviycyEtjsaEBpOf6Ne/OLRNu98un7WNDzMTQ==',
|
||||
'lWBTpWW8q80WC1eJEH5HMnGka4/LUF7zjUPqBwRMO0JzVcnjICvMP2TZP' \
|
||||
'B2lJfy/4ctIstCN6P1t38NCTTbLWlXuE',
|
||||
'https://contoso.com/wopi/files/vHxYyRGM8VfmSGwGYDBMIQPzuE' \
|
||||
'+sSC6kw+zWZw2Nyg?access_token=yZhdN1qgywcOQWhyEMVpB6NE3pv' \
|
||||
'BksvcLXsrFKXNtBeDTPW%2fu62g2t%2fOCWSlb3jUGaz1zc%2fzOzbNgA' \
|
||||
'redLdhQI1Q7sPPqUv2owO78olmN74DV%2fv52OZIkBG%2b8jqjwmUobcj' \
|
||||
'XVIC1BG9g%2fynMN0itZklL2x27Z2imCF6xELcQUuGdkoXBj%2bI%2bTlKM'
|
||||
)
|
||||
end
|
||||
|
||||
test 'Verify X-WOPI-Proof with current discovery proof key2.' do
|
||||
assert @discovery.verify_proof(
|
||||
'RLoY%2f3D73%2fjwt6IQqR1wHqCEKDxRf2v0GPDa0ZHTlA6ik1%2fNBHDD' \
|
||||
'6bHCI0BQrvacjNBL8ok%2fZsVPI%2beAIA5mHSOUbhW9ohowwD6Ljlwro2' \
|
||||
'n5PkTBh6GEYi2afuCIQ8mjXAUdvEDg3um2GjJKtA%3d%3d',
|
||||
635655897361394523,
|
||||
'x0IeSOjUQNH2pvjMPkP4Jotzs5Weeqms4AlPxMQ5CipssUJbyKFjLWnwPg' \
|
||||
'1Ac0XtSTiPD177BmQ1+KtmYvDTWQ1FmBuvpvKZDSKzXoT6Qj4LCYYQ0Txn' \
|
||||
'N/OT231+qd50sOD8zAxhfXP56qND9tj5xqoHMa+lbuvNCqiOBTZw5f/dkl' \
|
||||
'SK7Wgdx7ST3Dq6S9xxDUfsLC4Tjq+EsvcdSNIWL/W6NRZdyWqlgRgE6X8t' \
|
||||
'/2iyyMypURdOW2Rztc6w/iYhbuh22Ul6Jfu14KaDo6YkvBr8iHlK4CcQST' \
|
||||
'9i0u044y1Jnh34UK4EPdVRZrvTmeJ/5DFLWOqEwvBlW2bpoYF+9A==',
|
||||
'etYRI9UT6q8jA6PHMMmuGa8NbyIlbTHMHmJZqaCOh2GCpv7um2q7+7oaDF' \
|
||||
'qAV/UP+2N85yZXvZgt9kTOUCwIdggUQVeJluNCwf1B5NsN/7n2aQF9LnWy' \
|
||||
'YK8kK/3xvQKQrj4n24jk2MnHcX1tk8H/qLxq2DbPzi6ROoSgs2ZK8nmzhS' \
|
||||
'PF74jnLCrwiwGgnVZV6gIhlAKCcUGtdrT60sgD/wpJGQQ0M59VDQhf1aDj' \
|
||||
'5bUotf8RXovY8Gl0lpguvN4+EsEjpbVjdV9hxWs7c03JDdoz7mzFUWErSl' \
|
||||
'y9IzYXNfuFZMnSXpF3lGiprVJvW34Bu2gTo/cLq4LQoABkNCmd4g==',
|
||||
'https://contoso.com/wopi/files/JIB9h+LJpZWBDwvoIiQ5p3115zJ' \
|
||||
'WDecpGF9aCm1vOa5UMllgC7w?access_token=RLoY%2f3D73%2fjwt6IQ' \
|
||||
'qR1wHqCEKDxRf2v0GPDa0ZHTlA6ik1%2fNBHDD6bHCI0BQrvacjNBL8ok%' \
|
||||
'2fZsVPI%2beAIA5mHSOUbhW9ohowwD6Ljlwro2n5PkTBh6GEYi2afuCIQ8' \
|
||||
'mjXAUdvEDg3um2GjJKtA%3d%3d'
|
||||
)
|
||||
end
|
||||
|
||||
test 'Verify X-WOPI-ProofOld with current discovery proof key1.' do
|
||||
assert @discovery.verify_proof(
|
||||
'zo7pjAbo%2fyof%2bvtUJn5gXpYcSl7TSSx0TbQGJbWJSll9PTjRRsAbG%' \
|
||||
'2fSNL7FM2n5Ei3jQ8UJsW4RidT5R4tl1lrCi1%2bhGjxfWAC9pRRl6J3M1' \
|
||||
'wZk9uFkWEeGzbtGByTkaGJkBqgKV%2ffxg%2bvATAhVr6E3LHCBAN91Wi8UG',
|
||||
635655898374047766,
|
||||
'qQhMjQf9Zohj+S/wvhe+RD6W5TIEqJwDWO3zX9DB85yRe3Ide7EPQDCY9dA' \
|
||||
'ZtJpWkIDDzU+8FEwnexF0EhPimfCkmAyoPpkl2YYvQvvwUK2gdlk3WboWOV' \
|
||||
'szm17p4dSDA0TDMPYsjaAGHKM/nPnTyIMzRyArEzoy2vNkLEP6qdBIuMP2a' \
|
||||
'CtGsciwMjYifHYRIRenB7H7I+FkwH0UaoTUCoo2PJkyZjy1nK6OwGVWaWG0' \
|
||||
'G8g7Zy+K3bRYV+7cNaM5SB720ezhmYYJJvsIdRvO7pLsjAuTo4KJhvmVFCi' \
|
||||
'pwyCdllVHY83GjuGOsAbHIIohl0Ttq59o0jp4w2wUs8U+mQ==',
|
||||
'PjKR1BTNNnfOrUzfo27cLIhlrbSiOVZaANadDyHxKij/77ZYId+liyXoawv' \
|
||||
'vQQPgnBH1dW6jqpr6fh5ZxZ9IOtaV+cTSUGnGdRSn7FyKs1ClpApKsZBO/i' \
|
||||
'RBLXw3HDWOLc0jnA2bnxY8yqbEPmH5IBC9taYzxnf7aGjc6AWFHfs6AEQ8l' \
|
||||
'Mio6UoASNzjy3VVNzUX+CK+e5Z45coT0X60mjaJmidGfPdWIfyUw8sSuUwx' \
|
||||
'Qa1uNXAd8IceRUL7j5s9/kk7EwsihCw1Y3L+XJGG5zMsGhM9bTK5mvxj30U' \
|
||||
'dmZORouNHdywOfdHaB1iOeKOk+yvWFMW3JsYShWbUhZUOEQ==',
|
||||
'https://contoso.com/wopi/files/RVQ29k8tf3h8cJ/Endy+aAMPy0iG' \
|
||||
'hLatGNrhvKofPY9p2w?access_token=zo7pjAbo%2fyof%2bvtUJn5gXpY' \
|
||||
'cSl7TSSx0TbQGJbWJSll9PTjRRsAbG%2fSNL7FM2n5Ei3jQ8UJsW4RidT5R' \
|
||||
'4tl1lrCi1%2bhGjxfWAC9pRRl6J3M1wZk9uFkWEeGzbtGByTkaGJkBqgKV%' \
|
||||
'2ffxg%2bvATAhVr6E3LHCBAN91Wi8UG'
|
||||
)
|
||||
end
|
||||
|
||||
test 'Verify X-WOPI-ProofOld with current discovery proof key2.' do
|
||||
assert @discovery.verify_proof(
|
||||
'2rZvm60097vFfXK01cylEhaYU26CbpsoEdVdk%2fDK2JzBjg185nE69CCkl' \
|
||||
'Y36u2Thrx9DyLdNZGsbRorX12TK%2b6XW8ka%2fKSukXl9N3dkOLlZxe%2b' \
|
||||
'TQhhDlcymhYZ%2fnYx282ZrnI8gdIob0nmlYAIaSDRg0ZVx%2f%2bnSV8X8' \
|
||||
'cHsiMvftBkQQ%2bJgqOU1le3OimjHOiDg%3d%3d',
|
||||
635655899172093398,
|
||||
'nLwR4nbFq8F/RLuAQ0Yby/avSJcpktZh+JlWonkXuVunTw+BZ+F84c3i1+0' \
|
||||
'o7QoPQRkYmy/HEYhFoYVghkhQJzFXf98sYxb52SvDz/pl3G/gTgCcdcqkfR' \
|
||||
'SMtaslkRX1VKC3YX42ksIL0LzM4wiCOJfT70i+2Yr5EQRWi6b2JELrAqPuN' \
|
||||
'kxpuUUZ3DtEXsO9r6a4WkEwJesmqcqaQpMUvQ6cAkShgY7p0gAQeL//GG6w' \
|
||||
'SpDaaA3QqKBcmBgzAatCaqg9NXiPjviZdoHgnbIRjBEhtH/WG00py/tsGMo' \
|
||||
'Ih6R9VfnC4jEWWymB5mIiXxW1gBuL63QvQqiL1S8FQ2Xo+g==',
|
||||
'M7R/iGyWInpWKl1+aAPuB2yVAwWaHTi504c87V0p35PzDyTh+K6fn20ygKm' \
|
||||
'xf9suN9pq+/rMtUXhvM/zCyam0dZFuWpFOI22U0AjQxIJ4ZBH+zT+e/QeNm' \
|
||||
'S7w4ctxUnDJ0zGsJ/DxaC0/DAkiIMOfXgb8FMPA/Z/HY+e1C6rRsoMXy0XG' \
|
||||
'oTwzIDiI6wzPVr6FJpkktwO+eCNMyxnAODhlK+nubIhvmVEzWtenMXX8a2e' \
|
||||
'J3mFquNC7Le6shiUxZA03MgqknwjasNaGPrdpJYjxCwzH3LnHPOHxVY2Hcj' \
|
||||
'jTQTNHO8HEPCoSw9bi5a7XFS2loQM/tm0WknBe9+q4fpMMw==',
|
||||
'https://contoso.com/wopi/files/s/3BPN8mUjJJAQS+aOFtFm6CU+YN' \
|
||||
'pRICIR2M7mQLeQ90BA?access_token=2rZvm60097vFfXK01cylEhaYU26' \
|
||||
'CbpsoEdVdk%2fDK2JzBjg185nE69CCklY36u2Thrx9DyLdNZGsbRorX12TK' \
|
||||
'%2b6XW8ka%2fKSukXl9N3dkOLlZxe%2bTQhhDlcymhYZ%2fnYx282ZrnI8g' \
|
||||
'dIob0nmlYAIaSDRg0ZVx%2f%2bnSV8X8cHsiMvftBkQQ%2bJgqOU1le3Oim' \
|
||||
'jHOiDg%3d%3d'
|
||||
)
|
||||
end
|
||||
|
||||
test 'Verify X-WOPI-Proof with old discovery proof key1.' do
|
||||
assert @discovery.verify_proof(
|
||||
'pbocsujrb9BafFujWh%2fuh7Y6S5nBnonddEzDzV0zEFrBwhiu5lzjXRezX' \
|
||||
'DC9N4acvJeGVB5CWAcxPz6cJ6FzJmwA4ZgGP6FaV%2b6CDkJYID3FJhHFrb' \
|
||||
'w8f2kRfaceRjV1PzXEvFXulnz2K%2fwwv0rF2B4A1wGQrnmwxGIv9cL5PBC4',
|
||||
635655898062751632,
|
||||
'qF15pAAnOATqpUTLHIS/Z5K7OYFVjWcgKGbHPa0eHRayXsb6JKTelGQhvs7' \
|
||||
'4gEFgg1mIgcCORwAtMzLmEFmOHgrdvkGvRzT3jtVVtwkxEhQt8aQL20N0Nw' \
|
||||
'n4wNah0HeBHskdvmA1G/qcaFp8uTgHpRYFoBaSHEP3AZVNFg5y2jyYR34nN' \
|
||||
'j359gktc2ZyLel3J3j7XtyjpRPHvvYVQfh7RsArLQ0VGp8sL4/BDHdSsUyJ' \
|
||||
'8FXe67TSrz6TMZPwhEUR8dYHYek9qbQjC+wxPpo3G/yusucm1gHo0BjW/l3' \
|
||||
'6cI8FRmNs1Fqaeppxqu31FhR8dEl7w5dwefa9wOUKcChF6A==',
|
||||
'KmYzHJ9tu4SXfoiWzOkUIc0Bh8H3eJrA3OnDSbu2hT68EuLTp2vmvvFcHyH' \
|
||||
'IiO8DuKj7u13MxkpuUER6VSIJp3nYfm91uEE/3g61V3SzaeRXdnkcKUa5x+' \
|
||||
'ulKViECL2n4mpHzNnymxojFW5Y4lKUU4qEGzjE71K1DSFTU/CBkdqycsuy/' \
|
||||
'Oct8G4GhA3O4MynlCf64B9LIhlWe4G+hxZgxIO0pq7w/1SH27nvScWiljVq' \
|
||||
'gOAKr0Oidk/7sEfyBcOlerLgS/A00nJYYJk23DjrKGTKz1YY0CMEsROJCMi' \
|
||||
'W11caxr0aKseOYlfmb6K1RXxtmiDpJ2T4y8jintjEdzEWDA==',
|
||||
'https://contoso.com/wopi/files/DJNj59eQlM6BvwzAHkykiB1vNOWR' \
|
||||
'uxT487+guv3v7HexfA?access_token=pbocsujrb9BafFujWh%2fuh7Y6S' \
|
||||
'5nBnonddEzDzV0zEFrBwhiu5lzjXRezXDC9N4acvJeGVB5CWAcxPz6cJ6Fz' \
|
||||
'JmwA4ZgGP6FaV%2b6CDkJYID3FJhHFrbw8f2kRfaceRjV1PzXEvFXulnz2K' \
|
||||
'%2fwwv0rF2B4A1wGQrnmwxGIv9cL5PBC4',
|
||||
)
|
||||
end
|
||||
|
||||
test 'Verify X-WOPI-Proof with old discovery proof key2.' do
|
||||
assert @discovery.verify_proof(
|
||||
'%2foBUJRjT2EVSVaB0Bjl2BCC7bXkN674bwppkzj2H9Elj2G%2bVGl06EzV' \
|
||||
'gv62BqIW17gZZUweK%2fBPCOWN%2bbwoHy2BWES7J4OrDgFpErCsRmRkUr9' \
|
||||
'82LaFC2Nxb0%2bH6u6MrWfRMK5dX6w0cltOtRU%2fnsJ9JasAq0J2%2fEzh' \
|
||||
'RbQwIyMQ%3d',
|
||||
635655898147049273,
|
||||
'egEcu5FlU9q+u+d5WQ/Z1nYbttR+0iWsLh2BgiyzAq93sQFyvQ5XSCW5i/V' \
|
||||
'7rt9HDewnmvIXsak5ayeoX2aDNgsVM8gSI533tO9dv/33vBM6MAzNLXP3v5' \
|
||||
'rNI/TaTXcfm4Q1bIoE5RFVZUSPH73B3p1Oon0Hhhd1CysFGv6ue00CT1Dgp' \
|
||||
'u/w5cVElQxZmWmguD5hEFgmg9IGKuSJ1CdVeJLcTbdKWhJnTl/G4+SpXfWB' \
|
||||
'nvTBXSzLK+PdtfVCxh/bPO/FJNRWNAg+UgdVAXnLN79faiioG27lXGVrodp' \
|
||||
'VgW8qdANvDkjQbn7z7jXmQmv8ksVQgHfZel963NXRty5pDg==',
|
||||
'AQUuboqy0JFnMgWfaz8o6V3YDCd6fwQlmkM5cNxCK1ikLK03W4R3xjazBev' \
|
||||
'Nja70i3y0JYZ3GvTlJy4ZsEfYf+DRhfzgSuxExpTvNB5RMUvi/kNRTlrMNm' \
|
||||
'6JQrLr2rTY5A+FvQ8YErnKSl5gqh9vb4b47sRdQNvYL5hiVI+Qd4AGAwN9d' \
|
||||
'Sq/IJo3jHtivPSGMEfuqozoX3/GQEYfRf5dv58Rjeo5XRiDvuSDhPOacdoL' \
|
||||
'/DFc/ksC0u9vN4qmoTSvt313p/FhFZ9T3NZYi/dv63XP8DJLWs3HMauzGiK' \
|
||||
'FbvKaxDYb2K2llM1CRM2PbKproARMUR6oYYzwNKtg/Q7qIw==',
|
||||
'https://contoso.com/wopi/files/UzfEboK8w9Eal86Vk4xzIkGwUeMA' \
|
||||
'iFaNF14FfQETzQ72LPw?access_token=%2foBUJRjT2EVSVaB0Bjl2BCC7' \
|
||||
'bXkN674bwppkzj2H9Elj2G%2bVGl06EzVgv62BqIW17gZZUweK%2fBPCOWN' \
|
||||
'%2bbwoHy2BWES7J4OrDgFpErCsRmRkUr982LaFC2Nxb0%2bH6u6MrWfRMK5' \
|
||||
'dX6w0cltOtRU%2fnsJ9JasAq0J2%2fEzhRbQwIyMQ%3d'
|
||||
)
|
||||
end
|
||||
|
||||
test 'Invalid proof key1.' do
|
||||
assert_not @discovery.verify_proof(
|
||||
'7DHGrfxYtgXxfaXF%2benHF3PTGMrgNlYktkbQB8q%2fn2aQwzYQ6qTmqNJR' \
|
||||
'nJm5QIMXS7WbIxMy0LXaova2h687Md4%2bNTazty3P7HD3j5q9anbCuLsUJH' \
|
||||
'tSXfKANUetLyAjFWq6egtMZJSHzDajO0EaHTeA9M7zJg1j69dEMoLmIbxP03' \
|
||||
'kwAvBrdVQmjFdFryKw',
|
||||
635655899260461032,
|
||||
'Y/wVmPuvWJ5Q/Gl/a5mCkTrCKbfHWYiMG6cxmJgD+M/yYFzTsfcgbK2IRAlC' \
|
||||
'R2eqGx3a5wh5bQzlC6Y/sKo2IE9Irz/NHFpV55zYfdwDi5ccwXSd34jWVUgk' \
|
||||
'M3uL1r6KVmHcQH/ew10p54FVatXatuGp2Y+cq9BScYV1a45U8fs9zYoZcTAY' \
|
||||
'vdWeYXmJbRGMOLLxab3fOiulPmbw+gOqpYPbuInc0yut6eGxAUmY1ENxpN7n' \
|
||||
'bUqI3LbJvmE3PuX8Ifgg3RCsaFJEqC6JR36MjG+VqoKaFI6hh/ZWLR4y7FBa' \
|
||||
'rSmQBr2VlAHrcWCJIMXoOOKaHdsDfNCb3A24LFvdAQ==',
|
||||
'Pdbk1FoAB4zhxaDptfVOwTCmrNgWO6zdokoI3VYO8eshE9nJR1Rzr9K2666z' \
|
||||
'a29IfT050jJX0EBanIXAawL4rFA6swPHYQAzf3pWJqwvqIbaLYvi4104IBWh' \
|
||||
'm9XdZ7C1jDUmG8DgwbKrXZfg7xxZ/hzPlwEp5Y9ZijD/ReAgRs0Va8/ytWc3' \
|
||||
'AJ+121Q1Ss9U8vD08K5+wg1PVYyNa2YGBpVJbt2ZSt8dvuciWZujFDTzgLvR' \
|
||||
'r6w17kg6+jkiwJyz2ZIL6ytyiUE1oJzsbslIZN3yGHEcmXZZ8Xz5q8fzrLUV' \
|
||||
'mRx1kX6FE2QzRe4+6Q+qNeI8Ct7dj7JBBdbK2Jq+6A==',
|
||||
'https://contoso.com/wopi/files/Dy07US/uXVeOMwgyEqGqeVNnyOoaR' \
|
||||
'xR+es2atR08tZPMatf0gf0?access_token=7DHGrfxYtgXxfaXF%2benHF3' \
|
||||
'PTGMrgNlYktkbQB8q%2fn2aQwzYQ6qTmqNJRnJm5QIMXS7WbIxMy0LXaova2' \
|
||||
'h687Md4%2bNTazty3P7HD3j5q9anbCuLsUJHtSXfKANUetLyAjFWq6egtMZJ' \
|
||||
'SHzDajO0EaHTeA9M7zJg1j69dEMoLmIbxP03kwAvBrdVQmjFdFryKw'
|
||||
)
|
||||
end
|
||||
|
||||
test 'Invalid proof key2.' do
|
||||
assert_not @discovery.verify_proof(
|
||||
'itKqYThsIRPrrctbu%2bh7%2fMcYvNZiVKc5Un50ZqjKtvhVwz0XcObcEVm5' \
|
||||
'rghR4PmrUG0P28QnQTYrcQ65%2bLtrIfbTBferY4zhPKt9qKaf6HKEyRU7ut' \
|
||||
'szLoIG7P7XIhmdTo0RftsCAO3gLp4ZqZiQi4icNS6mMGYboNZ1pHaNCRmR31' \
|
||||
'oZjmqWgcknWow8',
|
||||
635655897692506269,
|
||||
'Bu+myEy45g0hyKJycD7BYoDH+s10yvuH79Iq3OLz4w/o7gT8orKRrZ4KggJU' \
|
||||
'WMzRPPSgg+CqEf6zdNm6kFrxf/zXCXTqa/bEzmJPNBby+r0jlyz2KFsOkxJ/' \
|
||||
'mzVMKJltG0le56NJim21ZU4AwN0KiB6TZ56ruu/ma014proZWJt/H0qye/pN' \
|
||||
'z5ZyYdyc1khgzKf/8o6sCgh6+VEZx93BjAUyMJr6OS9nYq1B8XGei/sE4xdg' \
|
||||
'brIGW0Jwjl2hxqBQTn1VL0jyljbjuG+vy83QaeBSB4TGLljHZFgYp6ARCX7v' \
|
||||
'3NHO0MicJtTpLdpsB9dlLkbms/BcJd1gK7m5dDJK1A==',
|
||||
'CDU9l+cyr82vrUnyhdTh1P4jDwL82c//XrTWU/rPeTyB9Ko6z2aNCs893GEB' \
|
||||
'3jICY7mDZ4t0sA5z/lpN83UyfH1uVqnEB92zCQsJTkXMdNG9dsK64G6RxIVb' \
|
||||
'mhJkV5xiJvERCRnJJTcQg4ymeXQ3cuXAcN9XhStK8FCxBhxGMJsmlY2tahxd' \
|
||||
'TEIHXHVagvdyB1UY0V1rv+OAOnCGaTs0yyBA0Mo5nmTSNF8VnYmqsmbXyyDP' \
|
||||
'JJTsqallyJPxvdrLPeiBq1JHMr3b8dgel1jfweDR4mD9pHn3O9eaHN8b6xlL' \
|
||||
'c+dIpwvcflzyhhEL5JKEUtO6EKbfakZTUeS86BuBZA==',
|
||||
'https://contoso.com/wopi/files/6XZhdjvt6/cdJvXdBI1bdlIkhIjJP' \
|
||||
'nb9HgWzZX7V2d8?access_token=itKqYThsIRPrrctbu%2bh7%2fMcYvNZi' \
|
||||
'VKc5Un50ZqjKtvhVwz0XcObcEVm5rghR4PmrUG0P28QnQTYrcQ65%2bLtrIf' \
|
||||
'bTBferY4zhPKt9qKaf6HKEyRU7utszLoIG7P7XIhmdTo0RftsCAO3gLp4ZqZ' \
|
||||
'iQi4icNS6mMGYboNZ1pHaNCRmR31oZjmqWgcknWow8'
|
||||
)
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue