Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: device as a distinct model from user #1736

Merged
merged 23 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3d32976
Change device to independent model, migrate user data
beque Dec 20, 2023
7b525b8
Fixes for extraction of devices from users
beque Dec 22, 2023
c95cdcd
Add admin device api, admin device fetcher, Fix device model
beque Jan 12, 2024
f0f6b4a
Add mobx device store
beque Jan 12, 2024
cd88ad7
Add device at eln admin area
beque Jan 12, 2024
4f53890
Add device datacollector tab
beque Jan 15, 2024
4d0445e
Add migrations for single datacollector and novnc fields
beque Jan 24, 2024
23db01f
Change datacollector and novnc fields, Add novnc tab and validation
beque Jan 24, 2024
c245261
Add device metadata tab, fetcher, api and store
beque Jan 25, 2024
5094eb3
Add tests for admin_device_api and admin_device_metadata_api, some fi…
beque Jan 30, 2024
0d57cb1
Fixes for new device, datacollector and novnc
beque Feb 1, 2024
ecc9337
Change database field names for datacollector lib
beque Feb 1, 2024
69c8d5b
Remove ununsed components, fetcher, api calls
beque Feb 2, 2024
19d264d
Add encryption / decription for device novnc password
beque Feb 5, 2024
b20f180
Remove encrypted password from devices
beque Feb 5, 2024
c41a68b
Fix missing device attributes
beque Feb 5, 2024
46d151a
Fix devices tests
beque Feb 5, 2024
04dea40
Fix datacollector dir in tests
beque Feb 5, 2024
04ff455
Add some fixes
beque Feb 6, 2024
03b0cf2
Add annotation to device model
beque Feb 6, 2024
6c9a800
Add datacollector user level selected to device views and tests
beque Feb 21, 2024
dfe86fd
Fixes for rubocop
beque Feb 21, 2024
199f2c7
Fix missing schema
beque Apr 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/api/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ def to_json_camel_case(val)
mount Labimotion::SegmentAPI
mount Labimotion::LabimotionHubAPI
mount Chemotion::InventoryAPI
mount Chemotion::AdminDeviceAPI
mount Chemotion::AdminDeviceMetadataAPI

if Rails.env.development?
add_swagger_documentation(info: {
Expand Down
257 changes: 37 additions & 220 deletions app/api/chemotion/admin_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,211 +28,6 @@ class AdminAPI < Grape::API
end
end

namespace :listDevices do
desc 'Find all devices'
get 'all' do
present Device.all.order('first_name, last_name'), with: Entities::DeviceEntity, root: 'devices'
end
end

namespace :device do
desc 'Get device by Id'
params do
requires :id, type: Integer, desc: 'device id'
end
route_param :id do
get do
present Device.find(params[:id]), with: Entities::DeviceEntity, root: 'device'
end
end
end

namespace :deviceMetadata do
desc 'Get deviceMetadata by device id'
params do
requires :device_id, type: Integer, desc: 'device id'
end
route_param :device_id do
get do
present DeviceMetadata.find_by(device_id: params[:device_id]), with: Entities::DeviceMetadataEntity, root: 'device_metadata'
end
end

desc 'Synchronize chemotion deviceMetadata to DataCite'
params do
requires :device_id, type: Integer, desc: 'device id'
end
route_param :device_id do
put 'sync_to_data_cite' do
device = Device.find(params[:device_id])
DataCite.sync_to_data_cite!(device)
present device.device_metadata, with: Entities::DeviceMetadataEntity, root: 'device_metadata'
rescue DataCite::Client::UnprocessableEntity => e
present(error: "Error from DataCite: #{e.message}")
rescue DataCite::Syncer::UnwriteableDoiPrefixError => e
present(error: "DOI #{device.device_metadata.doi} is not writeable at DataCite (system prefix: #{ENV['DATA_CITE_PREFIX']})")
end
end

desc 'create/update device metadata'
params do
requires :device_id, type: Integer, desc: 'device id'

optional :name, type: String, desc: 'device name'
optional :doi, type: String, desc: 'device doi'
optional :url, type: String, desc: 'device url'
optional :landing_page, type: String, desc: 'device landing_page'
optional :type, type: String, desc: 'device type'
optional :description, type: String, desc: 'device description'
optional :publisher, type: String, desc: 'device publisher'
optional :publication_year, type: Integer, desc: 'device publication year'
optional :data_cite_state, type: String, desc: 'state'
optional :owners, desc: 'device owners'
optional :manufacturers, desc: 'device manufacturers'
optional :dates, desc: 'device dates'
end
post do
attributes = declared(params, include_missing: false)
metadata = DeviceMetadata.find_or_initialize_by(device_id: attributes[:device_id])
new_record = metadata.new_record?
metadata.update!(attributes)
DataCite.find_and_create_at_chemotion!(metadata.device) if new_record
present metadata.reload, with: Entities::DeviceMetadataEntity, root: 'device_metadata'
rescue ActiveRecord::RecordInvalid => e
{ error: e.message }
end
end

namespace :sftpDevice do # rubocop:disable Metrics/BlockLength
desc 'Connect device via SFTP'
params do
requires :method, type: String
requires :host, type: String
requires :user, type: String
requires :authen, type: String
optional :key_name, type: String
end
post do
case params[:authen]
when 'password'
credentials = Rails.configuration.datacollectors.sftpusers.select do |e|
e[:user] == params[:user]
end.first
raise 'No match user credentials!' unless credentials

connect_sftp_with_password(
host: params[:host],
user: credentials[:user],
password: credentials[:password]
)
when 'keyfile'
connect_sftp_with_key(params)
end

{ lvl: 'success', msg: 'Test connection successfully.' }
rescue StandardError => e
{ lvl: 'error', msg: e.message }
end
end

namespace :removeDeviceMethod do
desc 'Remove device profile method'
params do
requires :id, type: Integer, desc: 'device id'
end
post do
device = Device.find(params[:id])
data = device.profile.data || {}
data.delete('method') if data['method']
data.delete('method_params') if data['method_params']
device.profile.update!(data: data)
present device, with: Entities::DeviceEntity, root: 'device'
end
end

namespace :updateDeviceMethod do # rubocop:disable Metrics/BlockLength
desc 'Update device profile method'
params do
requires :id, type: Integer
requires :data, type: Hash do
requires :method, type: String
requires :method_params, type: Hash do
requires :dir, type: String
optional :host, type: String
optional :user, type: String
optional :authen, type: String
optional :key_name, type: String
optional :number_of_files, type: Integer
optional :selected_user_level, type: Boolean
end
end
end

after_validation do
@p_method = params[:data][:method]
user_level_selected = params.dig(:data, :method_params, :user_level_selected)
if @p_method.end_with?('local')
p_dir = if user_level_selected
params[:data][:method_params][:dir].gsub(%r{/\{UserSubDirectories\}}, '')
else
params[:data][:method_params][:dir]
end
@pn = Pathname.new(p_dir)
error!('Dir is not a valid directory', 500) unless @pn.directory?

localpath = Rails.configuration.datacollectors.localcollectors.select do |e|
@pn.realpath.to_path.start_with?(e[:path])
end.first
localpath = @pn.realpath.to_path.sub(localpath[:path], '') unless localpath.nil?

error!('Dir is not in white-list for local data collection', 500) if localpath.nil?

end

if @p_method.end_with?('sftp') && user_level_selected
params[:data][:method_params][:dir].chomp!('/{UserSubDirectories}')
end

key_path(params[:data][:method_params][:key_name]) if @p_method.end_with?('sftp') && params[:data][:method_params][:authen] == 'keyfile'
end

post do
device = Device.find(params[:id])
data = device.profile.data || {}
params[:data][:method_params][:dir] = @pn.realpath.to_path if @p_method.end_with?('local')
new_profile = {
data: data.merge(params[:data] || {})
}
device.profile.update!(**new_profile) &&
new_profile || error!('profile update failed', 500)
end
end

namespace :editNovncSettings do
desc 'Edit device NoVNC settings'
params do
requires :id, type: Integer
requires :data, type: Hash do
optional :novnc, type: Hash do
optional :token, type: String
optional :target, type: String
optional :password, type: String
end
end
end
put do
profile = Device.find(params[:id]).profile
if params[:data][:novnc][:target].present?
updated = profile.data.merge(params[:data] || {})
profile.update!(data: updated)
else
profile.data.delete('novnc')
profile.save!
end
status 204
end
end

resource :group_device do
namespace :list do
desc 'fetch groups'
Expand All @@ -258,16 +53,23 @@ class AdminAPI < Grape::API
@group_params = declared(params, include_missing: false)
@root_type = @group_params.delete(:rootType)
@group_params[:email] ||= format('%<time>i@eln.edu', time: Time.now.getutc.to_i)
@group_params[:password] = Devise.friendly_token.first(8)
@group_params[:password_confirmation] = @group_params[:password]

if %w[Group].include?(@root_type)
@group_params[:password] = Devise.friendly_token.first(8)
@group_params[:password_confirmation] = @group_params[:password]
end
end
post do
new_obj = Group.new(@group_params) if %w[Group].include?(@root_type)
new_obj = Device.new(@group_params) if %w[Device].include?(@root_type)

begin
new_obj.save!
present new_obj, with: Entities::GroupDeviceEntity
if new_obj.is_a?(Device)
present new_obj, with: Entities::DeviceEntity
else
present new_obj, with: Entities::GroupDeviceEntity
end
rescue ActiveRecord::RecordInvalid => e
{ error: e.message }
end
Expand Down Expand Up @@ -301,22 +103,38 @@ class AdminAPI < Grape::API
when 'NodeAdd'
obj = Group.find(params[:id]) if %w[Group].include?(params[:rootType])
obj = Device.find(params[:id]) if %w[Device].include?(params[:rootType])
new_users = (params[:add_users] || []).map(&:to_i) - obj.users.pluck(:id) if %w[Person Group].include?(params[:actionType])
new_users = (params[:add_users] || []).map(&:to_i) - obj.devices.pluck(:id) if %w[Device].include?(params[:actionType])
if %w[Person Group].include?(params[:actionType])
new_users = (params[:add_users] || []).map(&:to_i) - obj.users.pluck(:id)
end
if %w[Device].include?(params[:actionType])
new_users = (params[:add_users] || []).map(&:to_i) - obj.devices.pluck(:id)
end
obj.users << Person.where(id: new_users) if %w[Person].include?(params[:actionType])
obj.users << Group.where(id: new_users) if %w[Group].include?(params[:actionType])
obj.devices << Device.where(id: new_users) if %w[Device].include?(params[:actionType])
obj.save!
present obj, with: Entities::GroupDeviceEntity, root: 'root'

if obj.is_a?(Device)
present obj, with: Entities::DeviceEntity, root: 'root'
else
present obj, with: Entities::GroupDeviceEntity, root: 'root'
end
when 'NodeDel'
obj = Group.find(params[:id]) if %w[Group].include?(params[:rootType])
obj = Device.find(params[:id]) if %w[Device].include?(params[:rootType])
rm_users = (params[:rm_users] || []).map(&:to_i)
obj.users.delete(User.where(id: rm_users)) if %w[Person].include?(params[:actionType])
obj.admins.delete(User.where(id: rm_users)) if %w[Group].include?(params[:rootType]) && %w[Person].include?(params[:actionType])
if params[:rootType] == 'Group' && params[:actionType] == 'Person'
obj.admins.delete(User.where(id: rm_users))
end
obj.devices.delete(Device.where(id: rm_users)) if %w[Device].include?(params[:actionType])
User.gen_matrix(rm_users) if rm_users&.length&.positive?
present obj, with: Entities::GroupDeviceEntity, root: 'root'

if obj.is_a?(Device)
present obj, with: Entities::DeviceEntity, root: 'root'
else
present obj, with: Entities::GroupDeviceEntity, root: 'root'
end
end
end
end
Expand All @@ -331,7 +149,7 @@ class AdminAPI < Grape::API
end
post do
%i[enableIds disableIds].each do |cat|
next unless params[cat].present?
next if params[cat].blank?

term_ids = params[cat].map { |t| t.split('|').first.strip }
ids = OlsTerm.where(term_id: term_ids).pluck(:id)
Expand All @@ -340,7 +158,7 @@ class AdminAPI < Grape::API

result_all = Entities::OlsTermEntity.represent(
OlsTerm.where(owl_name: params[:owl_name]).arrange_serializable(order: :label),
serializable: true
serializable: true,
)
OlsTerm.write_public_file(params[:owl_name], ols_terms: result_all)

Expand All @@ -357,7 +175,7 @@ class AdminAPI < Grape::API
end as ancestry
SQL
).arrange_serializable(order: :label),
serializable: true
serializable: true,
).unshift(
'key': params[:owl_name], 'title': '-- Recently selected --', selectable: false, 'children': []
)
Expand All @@ -366,7 +184,6 @@ class AdminAPI < Grape::API
end
end


namespace :importOlsTerms do
desc 'import OLS terms'
params do
Expand All @@ -387,14 +204,14 @@ class AdminAPI < Grape::API
# write original owl
result = Entities::OlsTermEntity.represent(
OlsTerm.where(owl_name: owl_name).arrange_serializable(order: :label),
serializable: true
serializable: true,
)
OlsTerm.write_public_file(owl_name, ols_terms: result)

# write edited owl
result = Entities::OlsTermEntity.represent(
OlsTerm.where(owl_name: owl_name, is_enabled: true).arrange_serializable(order: :label),
serializable: true
serializable: true,
).unshift(
'key': params[:name], 'title': '-- Recently selected --', selectable: false, 'children': []
)
Expand Down Expand Up @@ -424,7 +241,7 @@ class AdminAPI < Grape::API
present(
Delayed::Job.select('id, queue, handler, run_at, failed_at, attempts, priority, last_error'),
with: Entities::JobEntity,
root: :jobs
root: :jobs,
)
end

Expand Down
Loading
Loading