cnfs-storage 0.0.1.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +53 -0
- data/Rakefile +22 -0
- data/app/controllers/column_maps_controller.rb +4 -0
- data/app/controllers/concerns/creatable_attachment.rb +14 -0
- data/app/controllers/documents_controller.rb +16 -0
- data/app/controllers/images_controller.rb +15 -0
- data/app/controllers/reports_controller.rb +4 -0
- data/app/controllers/storage/application_controller.rb +6 -0
- data/app/controllers/transfer_maps_controller.rb +4 -0
- data/app/jobs/platform_event_processor.rb +41 -0
- data/app/jobs/storage/application_job.rb +6 -0
- data/app/models/column_map.rb +17 -0
- data/app/models/concerns/has_attachment.rb +77 -0
- data/app/models/document.rb +75 -0
- data/app/models/image.rb +6 -0
- data/app/models/report.rb +4 -0
- data/app/models/sftp_file.rb +17 -0
- data/app/models/storage/application_record.rb +7 -0
- data/app/models/tenant.rb +28 -0
- data/app/models/transfer_map.rb +35 -0
- data/app/operations/iam_public_key_process.rb +39 -0
- data/app/policies/column_map_policy.rb +4 -0
- data/app/policies/document_policy.rb +4 -0
- data/app/policies/image_policy.rb +4 -0
- data/app/policies/report_policy.rb +4 -0
- data/app/policies/storage/application_policy.rb +6 -0
- data/app/policies/transfer_map_policy.rb +4 -0
- data/app/resources/column_map_resource.rb +6 -0
- data/app/resources/document_resource.rb +26 -0
- data/app/resources/image_resource.rb +18 -0
- data/app/resources/report_resource.rb +5 -0
- data/app/resources/storage/application_resource.rb +7 -0
- data/app/resources/transfer_map_resource.rb +6 -0
- data/app/workers/aws/storage_worker.rb +23 -0
- data/config/environment.rb +0 -0
- data/config/routes.rb +9 -0
- data/config/settings.yml +19 -0
- data/config/shoryuken.yml +3 -0
- data/config/sidekiq.yml +3 -0
- data/config/spring.rb +3 -0
- data/db/migrate/20190609160652_create_transfer_maps.rb +14 -0
- data/db/migrate/20190609160743_create_column_maps.rb +13 -0
- data/db/migrate/20190609161139_create_documents.rb +13 -0
- data/db/migrate/20190927024852_create_images.rb +10 -0
- data/db/migrate/20190927074408_create_reports.rb +9 -0
- data/db/migrate/20190929005520_create_sftp_files.rb +10 -0
- data/db/migrate/20191220075835_add_count_to_document.rb +11 -0
- data/db/seeds/development/data.seeds.rb +22 -0
- data/db/seeds/development/tenants.seeds.rb +7 -0
- data/lib/ros/storage.rb +27 -0
- data/lib/ros/storage/engine.rb +61 -0
- data/lib/ros/storage/version.rb +7 -0
- data/lib/tasks/ros/storage_tasks.rake +19 -0
- metadata +186 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 320ca2f0d4a12858f2db9f48aac931723947b3506951a60b93fb2d8e2d703568
|
|
4
|
+
data.tar.gz: 7d83ff85d9564d2285d7148735dfa29e204e61105c814bc11584e2a41050993c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1287066d9b909267badcee971356056a9f1c2823c92a6c107439e60eaed6b7d1be920ee5c7a794592bdab70989e70f1f445c8f46c497263bda51c669ec9b9964
|
|
7
|
+
data.tar.gz: eaab31da434bd7c092aea7da8e377bf946e2933e5b0560daf85069d143e7c6f6e9b03ef2809bc67754e831e20545a23e6a8fb038f0778bce957777910ff4ad6b
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2019 TODO: Write your name
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Storage
|
|
2
|
+
|
|
3
|
+
Storage service for Cloud Native Full Stack
|
|
4
|
+
|
|
5
|
+
Handles file uploads and downloads by SFTP and API
|
|
6
|
+
|
|
7
|
+
SFTP server mounts cloud storage, eg S3 bucket on AWS
|
|
8
|
+
|
|
9
|
+
File uploads trigger a message to eg SQS
|
|
10
|
+
|
|
11
|
+
This storage service receives the event and processes the file
|
|
12
|
+
|
|
13
|
+
Depending on the file fingerprint, the storage service notifies the relevant service of a new file
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Development
|
|
17
|
+
|
|
18
|
+
Ensure the SFTP server, IAM, Congito, Comm and Storage services are running
|
|
19
|
+
|
|
20
|
+
sftp -P 2222 222222222@localhost
|
|
21
|
+
|
|
22
|
+
### Pre-requisites
|
|
23
|
+
|
|
24
|
+
IAM Service creates tenant locations on cloud storage
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
Add this line to your application's Gemfile:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
gem 'ros-storage'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
And then execute:
|
|
37
|
+
```bash
|
|
38
|
+
$ bundle
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or install it yourself as:
|
|
42
|
+
```bash
|
|
43
|
+
$ gem install storage
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Contributing
|
|
47
|
+
Contribution directions go here.
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
51
|
+
|
|
52
|
+
# Documentation
|
|
53
|
+
[Rails on Services Guides](https://guides.rails-on-services.org)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require 'rdoc/task'
|
|
8
|
+
|
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
11
|
+
rdoc.title = 'Storage'
|
|
12
|
+
rdoc.options << '--line-numbers'
|
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
|
18
|
+
load 'rails/tasks/engine.rake'
|
|
19
|
+
|
|
20
|
+
load 'rails/tasks/statistics.rake'
|
|
21
|
+
|
|
22
|
+
require 'bundler/gem_tasks'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CreatableAttachment
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
def create
|
|
7
|
+
file = model_class.upload(io: params[:file])
|
|
8
|
+
render status: :ok, json: json_resources(resource_class: FileResource, records: file)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def model_class
|
|
12
|
+
self.class.name.gsub('Controller', '').singularize.constantize
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DocumentsController < Storage::ApplicationController
|
|
4
|
+
include CreatableAttachment
|
|
5
|
+
|
|
6
|
+
# TODO: move this to an operation
|
|
7
|
+
def create
|
|
8
|
+
file = model_class.upload(io: params[:file])
|
|
9
|
+
if file.persisted?
|
|
10
|
+
render status: :ok, json: serialize_resource(DocumentResource, DocumentResource.new(file, context))
|
|
11
|
+
else
|
|
12
|
+
resource = ApplicationResource.new(file, nil)
|
|
13
|
+
handle_exceptions JSONAPI::Exceptions::ValidationErrors.new(resource)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ImagesController < Storage::ApplicationController
|
|
4
|
+
include CreatableAttachment
|
|
5
|
+
|
|
6
|
+
def create
|
|
7
|
+
file = model_class.upload(io: params[:file])
|
|
8
|
+
if file.persisted?
|
|
9
|
+
render status: :ok, json: serialize_resource(ImageResource, ImageResource.new(file, context))
|
|
10
|
+
else
|
|
11
|
+
resource = ApplicationResource.new(file, nil)
|
|
12
|
+
handle_exceptions JSONAPI::Exceptions::ValidationErrors.new(resource)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# TODO: Move to the storage service
|
|
4
|
+
class PlatformEventProcessor
|
|
5
|
+
# Handle an update to an IAM Credential
|
|
6
|
+
def self.iam_credential(urn:, event:, data:)
|
|
7
|
+
puts urn
|
|
8
|
+
puts event
|
|
9
|
+
puts data
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.iam_user_group(urn:, event:, data:)
|
|
13
|
+
puts urn
|
|
14
|
+
puts event
|
|
15
|
+
puts data
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.iam_group_policy_join(urn:, event:, data:)
|
|
19
|
+
puts urn
|
|
20
|
+
puts event
|
|
21
|
+
puts data
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.iam_user(urn:, event:, data:)
|
|
25
|
+
puts urn
|
|
26
|
+
puts event
|
|
27
|
+
puts data
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.tenant(urn:, event:, data:)
|
|
31
|
+
puts urn
|
|
32
|
+
puts event
|
|
33
|
+
puts data
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.storage_transfer_map(urn:, event:, data:)
|
|
37
|
+
puts urn
|
|
38
|
+
puts event
|
|
39
|
+
puts data
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ColumnMap < Storage::ApplicationRecord
|
|
4
|
+
belongs_to :transfer_map
|
|
5
|
+
validate :column_name_is_eligible, if: -> { Ros.api_calls_enabled }
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def column_name_is_eligible
|
|
10
|
+
errors.add(:illegible_column_name, "#{name} is illegible column_name") unless service_columns.include?(name)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def service_columns
|
|
14
|
+
@service_columns ||=
|
|
15
|
+
transfer_map.service_name.constantize.where(model_name: transfer_map.target).first['model_columns']
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HasAttachment
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
has_one_attached :file
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def after_attach(io); end
|
|
11
|
+
|
|
12
|
+
def upload(io:)
|
|
13
|
+
file.purge # ensure any existing attachment is removed
|
|
14
|
+
self.class.upload(io: io, owner: self)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class_methods do
|
|
18
|
+
def upload(io:, owner: nil)
|
|
19
|
+
owner ||= create
|
|
20
|
+
io_filename = io.path if io.respond_to?(:path)
|
|
21
|
+
io_filename = io.original_filename if io.respond_to?(:original_filename)
|
|
22
|
+
# Set the bucket before uploading file
|
|
23
|
+
Rails.logger.debug("Setting bucket to #{bucket_name}")
|
|
24
|
+
service.set_bucket(bucket_name)
|
|
25
|
+
upload = ActiveStorage::Blob.new.tap do |blob|
|
|
26
|
+
blob.filename = io_filename
|
|
27
|
+
blob.key = "#{object_root}/#{blob_key(owner, blob)}"
|
|
28
|
+
blob.upload(io)
|
|
29
|
+
blob.save!
|
|
30
|
+
end
|
|
31
|
+
owner.file.attach(upload)
|
|
32
|
+
owner.after_attach(io)
|
|
33
|
+
Rails.logger.debug("Uploaded #{upload.key} to bucket #{bucket_name}")
|
|
34
|
+
owner
|
|
35
|
+
rescue Aws::S3::Errors::NoSuchBucket => e
|
|
36
|
+
# TODO: This should send an exception report to sentry
|
|
37
|
+
owner.errors.add(:file, e.message)
|
|
38
|
+
owner.delete
|
|
39
|
+
rescue ActiveRecord::RecordNotUnique => e
|
|
40
|
+
# TODO: This should send an exception report to sentry
|
|
41
|
+
owner.errors.add(:file, e.message)
|
|
42
|
+
owner.delete
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
owner.errors.add(:file, e.message)
|
|
45
|
+
owner.delete
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def blob_key(_owner, blob)
|
|
49
|
+
blob.class.generate_unique_secure_token
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def object_root
|
|
53
|
+
@object_root ||= Pathname.new([feature_set, object_scope, tenant_schema, object_dir].compact.join('/'))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# NOTE: feature_set will have a value for uat and blank for all others (dev, test, staging and production)
|
|
57
|
+
def feature_set; Settings.feature_set end
|
|
58
|
+
|
|
59
|
+
def object_scope; 'tenants' end
|
|
60
|
+
|
|
61
|
+
# TODO: if we need the tenant in a different format, that should be the responsibility of the Tenant model
|
|
62
|
+
def tenant_schema; Apartment::Tenant.current.gsub('_', '') end
|
|
63
|
+
|
|
64
|
+
# Examples: uploads, downloads; used on sftp service
|
|
65
|
+
def object_dir; nil end
|
|
66
|
+
|
|
67
|
+
delegate :name, to: :bucket, prefix: true
|
|
68
|
+
|
|
69
|
+
def bucket; Settings.infra.resources.storage.buckets[bucket_service] end
|
|
70
|
+
|
|
71
|
+
def bucket_service; Settings.infra.resources.storage.services[service_name] end
|
|
72
|
+
|
|
73
|
+
def service_name; table_name end
|
|
74
|
+
|
|
75
|
+
def service; ActiveStorage::Blob.service end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Document < Storage::ApplicationRecord
|
|
4
|
+
include HasAttachment
|
|
5
|
+
|
|
6
|
+
belongs_to :transfer_map, optional: true
|
|
7
|
+
|
|
8
|
+
def self.object_dir; 'uploads' end
|
|
9
|
+
|
|
10
|
+
def self.blob_key(_owner, blob)
|
|
11
|
+
"#{Time.zone.now.strftime('%F %T')}-#{blob.filename}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Takes array of events from SQS worker with keys to attached files. For each event/file it:
|
|
15
|
+
# creates an A/S blob and a Document record to attach the blob to
|
|
16
|
+
# It then downloads the blob, extracts the header and calls identify_transfer_map
|
|
17
|
+
def self.attach_from_storage_events(events)
|
|
18
|
+
events.each do |event|
|
|
19
|
+
Rails.logger.debug { "Document received event #{event}" }
|
|
20
|
+
if event.type.eql?('created') && event.size.positive?
|
|
21
|
+
Rails.logger.debug { 'Processing created event' }
|
|
22
|
+
Tenant.find_by(schema_name: event.schema_name).switch do
|
|
23
|
+
blob = ActiveStorage::Blob.create(key: event.key, filename: File.basename(event.key),
|
|
24
|
+
content_type: 'text/csv', byte_size: event.size, checksum: event.etag)
|
|
25
|
+
document = Document.create
|
|
26
|
+
document.file.attach(blob)
|
|
27
|
+
# download the blob, write it to a temp file and read the header
|
|
28
|
+
header = Tempfile.create do |f|
|
|
29
|
+
f << document.file.download
|
|
30
|
+
f.rewind
|
|
31
|
+
f.readline
|
|
32
|
+
end.chomp
|
|
33
|
+
document.update(header: header)
|
|
34
|
+
enqueue if document.identify_transfer_map
|
|
35
|
+
end
|
|
36
|
+
elsif event.type.eql? 'download'
|
|
37
|
+
Rails.logger.debug { 'Processing download event' }
|
|
38
|
+
else
|
|
39
|
+
Rails.logger.debug { 'NOT Processing unknown event' }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# For an HTTP upload the uploaded file is already on the local filesystem referenced by the io param
|
|
45
|
+
# so we just need to open the io object and read the first line
|
|
46
|
+
def after_attach(io)
|
|
47
|
+
# Remove BOM: https://estl.tech/of-ruby-and-hidden-csv-characters-ef482c679b35
|
|
48
|
+
update(header: File.open(io.tempfile, &:readline).chomp.gsub("\xEF\xBB\xBF", ''))
|
|
49
|
+
enqueue if identify_transfer_map
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def identify_transfer_map
|
|
53
|
+
file_columns = header.split(',').map(&:strip)
|
|
54
|
+
return unless (transfer_map_id = TransferMap.match(file_columns)&.id)
|
|
55
|
+
|
|
56
|
+
update(transfer_map_id: transfer_map_id)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def enqueue
|
|
60
|
+
Ros::StorageDocumentProcessJob.set(queue: target_service_queue).perform_later(job_payload.to_json)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Service name where the job will be enqueued
|
|
64
|
+
def target_service_queue; "#{transfer_map.service}_default" end
|
|
65
|
+
|
|
66
|
+
def job_payload; attributes.slice('id') end
|
|
67
|
+
|
|
68
|
+
def column_map
|
|
69
|
+
return [] unless transfer_map
|
|
70
|
+
|
|
71
|
+
transfer_map.column_maps.pluck(:user_name, :name).each_with_object({}) do |(key, value), hash|
|
|
72
|
+
hash[key.to_sym] = value
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/app/models/image.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SftpFile < ApplicationRecord
|
|
4
|
+
include HasAttachment
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def object_scope; 'services' end
|
|
8
|
+
|
|
9
|
+
def tenant_schema; 'sftp' end
|
|
10
|
+
|
|
11
|
+
def service_name; 'sftp' end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.blob_key(owner, _blob)
|
|
15
|
+
owner.key
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Tenant < Storage::ApplicationRecord
|
|
4
|
+
include Ros::TenantConcern
|
|
5
|
+
|
|
6
|
+
after_commit :write_tenants_to_sftp_users_conf
|
|
7
|
+
|
|
8
|
+
def write_tenants_to_sftp_users_conf
|
|
9
|
+
self.class.write_tenants_to_sftp_users_conf
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.write_tenants_to_sftp_users_conf
|
|
13
|
+
Apartment::Tenant.switch('public') do
|
|
14
|
+
SftpFile.find_or_create_by!(key: 'config/users.conf').upload(io: File.open(sftp_user_content))
|
|
15
|
+
end
|
|
16
|
+
rescue StandardError
|
|
17
|
+
Rails.logger.warn('error')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.sftp_user_content
|
|
21
|
+
file = Tempfile.new('users.conf')
|
|
22
|
+
order(:id).all.each do |tenant|
|
|
23
|
+
file.write("#{tenant.account_id}:pass:#{tenant.account_id}::uploads,downloads\n")
|
|
24
|
+
end
|
|
25
|
+
file.close
|
|
26
|
+
file
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TransferMap < Storage::ApplicationRecord
|
|
4
|
+
has_many :column_maps
|
|
5
|
+
validates :name, :service, :target, presence: true
|
|
6
|
+
validate :service_is_valid, if: -> { Ros.api_calls_enabled }
|
|
7
|
+
validate :target_is_valid, if: -> { Ros.api_calls_enabled }
|
|
8
|
+
|
|
9
|
+
def self.match(columns_to_match)
|
|
10
|
+
columns_to_match.sort!
|
|
11
|
+
all.each do |tmap|
|
|
12
|
+
columns = tmap.column_maps.pluck(:user_name).sort
|
|
13
|
+
return tmap if columns.eql?(columns_to_match)
|
|
14
|
+
end
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def service_name
|
|
19
|
+
"Ros::#{service&.classify}::FileFingerprint"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def service_is_valid
|
|
25
|
+
service_name.constantize
|
|
26
|
+
rescue NameError
|
|
27
|
+
errors.add(:invalid_service_name, "#{service_name} is invalid service name")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def target_is_valid
|
|
31
|
+
raise unless service_name.constantize.where(model_name: target).any?
|
|
32
|
+
rescue StandardError
|
|
33
|
+
errors.add(:invalid_target, "#{target} is invalid target for #{service_name}")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class IamPublicKeyProcess
|
|
4
|
+
def self.call(params)
|
|
5
|
+
new.call(params: params)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
METADATA_HASH = { mode: '33188', gid: '100' }.freeze
|
|
9
|
+
|
|
10
|
+
def call(_json)
|
|
11
|
+
Ros::Infra.resources.storage.app.cp(source_path, target_path, METADATA_HASH.merge(uid: tenant.account_id))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def source_path; "fs:#{key_file.path.gsub("#{fs_path}/", '')}" end
|
|
15
|
+
|
|
16
|
+
def target_path; "#{SftpFile.object_root}/config/sshx/authorized-keys/#{tenant.account_id}" end
|
|
17
|
+
|
|
18
|
+
def tenant
|
|
19
|
+
Tenant.find_by(schema_name: Apartment::Tenant.current)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def key_file
|
|
23
|
+
@key_file ||= tempfile
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def tempfile
|
|
27
|
+
file = Tempfile.new('public_key', fs_path)
|
|
28
|
+
file.write(content)
|
|
29
|
+
file.close
|
|
30
|
+
file
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# TODO: Handle pagination
|
|
34
|
+
def content
|
|
35
|
+
@content ||= Ros::IAM::PublicKey.select(:content).all.map(&:content).join("\n")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fs_path; Rails.root.join('tmp', 'fs') end
|
|
39
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DocumentResource < Storage::ApplicationResource
|
|
4
|
+
attributes :transfer_map, :target, :column_map, :blob, :platform_event_state,
|
|
5
|
+
:url, :status, :processed_amount, :success_amount, :fail_amount, :processed_details
|
|
6
|
+
|
|
7
|
+
def transfer_map
|
|
8
|
+
@model.transfer_map&.name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def target
|
|
12
|
+
@model.transfer_map&.target
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def column_map
|
|
16
|
+
@model.column_map
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def url
|
|
20
|
+
@model.file.attached? ? Ros::Infra.resources.storage.app.presigned_url(@model.file.blob.key) : ''
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def blob
|
|
24
|
+
JSON.parse((@model.file.attached? ? @model.file.blob : {}).to_json)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ImageResource < Storage::ApplicationResource
|
|
4
|
+
attributes :bucket_name, :blob, :cdn, :url
|
|
5
|
+
|
|
6
|
+
# TODO: refactor this inefficent way to get the CDN
|
|
7
|
+
def cdn
|
|
8
|
+
Settings.infra.resources.cdns.to_hash.dup.select do |_k, v|
|
|
9
|
+
v[:bucket].eql?(@model.class.bucket_service)
|
|
10
|
+
end.first.last[:url]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def url; "#{cdn}/#{@model.file.attached? ? @model.file.blob.key : ''}" end
|
|
14
|
+
|
|
15
|
+
def bucket_name; @model.class.bucket_name end
|
|
16
|
+
|
|
17
|
+
def blob; JSON.parse((@model.file.attached? ? @model.file.blob : {}).to_json) end
|
|
18
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
if defined?(Shoryuken)
|
|
4
|
+
require_relative '../../../spec/dummy/config/application'
|
|
5
|
+
Rails.application.initialize!
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module Aws
|
|
9
|
+
class StorageWorker
|
|
10
|
+
if defined?(Shoryuken)
|
|
11
|
+
include Shoryuken::Worker
|
|
12
|
+
shoryuken_options queue: Ros::Infra.resources.mq.storage_data.name, auto_delete: true
|
|
13
|
+
|
|
14
|
+
Rails.logger.debug("Configured to receive events from queue: #{Ros::Infra.resources.mq.storage_data.name}")
|
|
15
|
+
|
|
16
|
+
# Process a lifecycle event from the S3 bucket
|
|
17
|
+
def perform(_sqs_msg, payload)
|
|
18
|
+
message = Ros::Infra::Aws::StorageMessage.new(payload: payload)
|
|
19
|
+
Document.attach_from_storage_events(message.events)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
File without changes
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Storage::Engine.routes.draw do
|
|
4
|
+
jsonapi_resources :column_maps
|
|
5
|
+
jsonapi_resources :documents
|
|
6
|
+
jsonapi_resources :images, only: %i[index show create]
|
|
7
|
+
jsonapi_resources :reports, only: %i[index show]
|
|
8
|
+
jsonapi_resources :transfer_maps
|
|
9
|
+
end
|
data/config/settings.yml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
infra:
|
|
3
|
+
resources:
|
|
4
|
+
mq:
|
|
5
|
+
queues:
|
|
6
|
+
storage_data:
|
|
7
|
+
name: <%= ENV['PLATFORM__INFRA__RESOURCES__MQ__QUEUES__STORAGE_DATA__BUCKET_NAME'] %>-<%= ENV['PLATFORM__FEATURE_SET'] %>-storage-data
|
|
8
|
+
storage:
|
|
9
|
+
buckets:
|
|
10
|
+
app:
|
|
11
|
+
notifications:
|
|
12
|
+
queue_configurations:
|
|
13
|
+
- queue_arn: arn:aws:sqs:<%= ENV['AWS_DEFAULT_REGION'] %>:<%= ENV['AWS_ACCOUNT_ID'] %>:<%= ENV['PLATFORM__INFRA__RESOURCES__MQ__QUEUES__STORAGE_DATA__BUCKET_NAME'] %>-<%= ENV['PLATFORM__FEATURE_SET'] %>-storage-data
|
|
14
|
+
events: ['s3:ObjectCreated:*']
|
|
15
|
+
filter:
|
|
16
|
+
key:
|
|
17
|
+
filter_rules:
|
|
18
|
+
- { name: 'prefix', value: '<%= ENV['PLATFORM__FEATURE_SET'] %>/services/storage/tenants' }
|
|
19
|
+
- { name: 'suffix', value: '.csv' }
|
data/config/sidekiq.yml
ADDED
data/config/spring.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateTransferMaps < ActiveRecord::Migration[6.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :transfer_maps do |t|
|
|
6
|
+
t.string :name
|
|
7
|
+
t.string :description
|
|
8
|
+
t.string :service
|
|
9
|
+
t.string :target
|
|
10
|
+
|
|
11
|
+
t.timestamps
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateColumnMaps < ActiveRecord::Migration[6.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :column_maps do |t|
|
|
6
|
+
t.references :transfer_map, null: false, foreign_key: true
|
|
7
|
+
t.string :name
|
|
8
|
+
t.string :user_name
|
|
9
|
+
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddCountToDocument < ActiveRecord::Migration[6.0]
|
|
4
|
+
def change
|
|
5
|
+
add_column :documents, :status, :string
|
|
6
|
+
add_column :documents, :processed_amount, :integer
|
|
7
|
+
add_column :documents, :success_amount, :integer
|
|
8
|
+
add_column :documents, :fail_amount, :integer
|
|
9
|
+
add_column :documents, :processed_details, :jsonb
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Lint/UselessAssignment
|
|
4
|
+
after 'development:tenants' do
|
|
5
|
+
Tenant.all.each do |tenant|
|
|
6
|
+
is_even = (tenant.id % 2).zero?
|
|
7
|
+
next if tenant.id.eql? 1
|
|
8
|
+
|
|
9
|
+
tenant.switch do
|
|
10
|
+
Settings.api_calls_enabled = false
|
|
11
|
+
TransferMap.create(name: 'Customer List', description: '', service: 'cognito', target: 'user').tap do |map|
|
|
12
|
+
map.column_maps.create(name: 'title', user_name: 'Salutation')
|
|
13
|
+
map.column_maps.create(name: 'last_name', user_name: 'Last Name')
|
|
14
|
+
map.column_maps.create(name: 'phone_number', user_name: 'Mobile')
|
|
15
|
+
map.column_maps.create(name: 'primary_identifier', user_name: 'Unique Number')
|
|
16
|
+
map.column_maps.create(name: 'pool_name', user_name: 'Campaign')
|
|
17
|
+
end
|
|
18
|
+
Settings.api_calls_enabled = true
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
# rubocop:enable Lint/UselessAssignment
|
data/lib/ros/storage.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ros/core'
|
|
4
|
+
require 'storage/engine'
|
|
5
|
+
|
|
6
|
+
module Ros
|
|
7
|
+
class << self
|
|
8
|
+
def excluded_table_names
|
|
9
|
+
%w[schema_migrations ar_internal_metadata tenant_events platform_events active_storage_blobs
|
|
10
|
+
active_storage_attachments sftp_files]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def excluded_models; %w[Tenant SftpFile] end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Storage
|
|
18
|
+
module Methods
|
|
19
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
20
|
+
def set_bucket(bucket)
|
|
21
|
+
return unless @client
|
|
22
|
+
|
|
23
|
+
@bucket = @client.bucket(bucket)
|
|
24
|
+
end
|
|
25
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Storage
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
config.generators.api_only = true
|
|
6
|
+
config.generators do |g|
|
|
7
|
+
g.test_framework :rspec, fixture: true
|
|
8
|
+
g.fixture_replacement :factory_bot, dir: 'spec/factories'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
initializer 'service.set_storage_config' do |_app|
|
|
12
|
+
ActiveStorage::Service.module_eval { attr_writer :bucket }
|
|
13
|
+
ActiveStorage::Service.class_eval { include Storage::Methods }
|
|
14
|
+
# Read a block from config/storage.yml for the storage adapter to use
|
|
15
|
+
# config = Rails.application.config.active_storage.service_configurations[storage_key_from_env]
|
|
16
|
+
# app.config.active_storage.service = Rails.env.to_sym if Rails.env.development?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
initializer 'service.set_platform_config', before: 'ros_core.load_platform_config' do |_app|
|
|
20
|
+
settings_path = root.join('config/settings.yml').to_s
|
|
21
|
+
Settings.prepend_source!(settings_path) if File.exist?(settings_path)
|
|
22
|
+
name = self.class.module_parent.name.demodulize.underscore
|
|
23
|
+
Settings.prepend_source!(service: { name: name, policy_name: name.capitalize })
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
initializer 'service.initialize_infra_services', after: 'ros_core.initialize_infra_services' do |_app|
|
|
27
|
+
# AWS SQS Workers
|
|
28
|
+
if defined?(Shoryuken)
|
|
29
|
+
Shoryuken.configure_server do |config|
|
|
30
|
+
config.sqs_client = Ros::Infra.resources.mq.storage_data.client
|
|
31
|
+
Rails.logger.debug("Configured SQS worker with #{config.options}")
|
|
32
|
+
end
|
|
33
|
+
# elsif defined?(GcpQueueWorker)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
initializer 'service.configure_event_logging' do |_app|
|
|
38
|
+
if Settings.event_logging.enabled
|
|
39
|
+
Settings.event_logging.config.schemas_path = root.join(Settings.event_logging.config.schemas_path)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Adds this gem's db/migrations path to the enclosing application's migraations_path array
|
|
44
|
+
# if the gem has been included in an application, i.e. it is not running in the dummy app
|
|
45
|
+
# https://github.com/rails/rails/issues/22261
|
|
46
|
+
initializer 'service.configure_migrations' do |app|
|
|
47
|
+
unless Rails.root.to_s.end_with?('spec/dummy')
|
|
48
|
+
config.paths['db/migrate'].expanded.each do |expanded_path|
|
|
49
|
+
app.config.paths['db/migrate'] << expanded_path
|
|
50
|
+
ActiveRecord::Migrator.migrations_paths << expanded_path
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
initializer 'service.set_factory_paths', after: 'ros_core.set_factory_paths' do
|
|
56
|
+
if defined?(FactoryBot) && !Rails.env.production?
|
|
57
|
+
FactoryBot.definition_file_paths.prepend(Pathname.new(__FILE__).join('../../../spec/factories'))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# create_file rake_file do <<-RUBY
|
|
2
|
+
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
namespace :ros do
|
|
6
|
+
namespace :storage do
|
|
7
|
+
# namespace :#{@profile.service_name} do
|
|
8
|
+
namespace :db do
|
|
9
|
+
desc 'Load engine seeds'
|
|
10
|
+
task :seed do
|
|
11
|
+
seedbank_root = Seedbank.seeds_root
|
|
12
|
+
Seedbank.seeds_root = File.expand_path('db/seeds', Storage::Engine.root)
|
|
13
|
+
Seedbank.load_tasks
|
|
14
|
+
Rake::Task['db:seed'].invoke
|
|
15
|
+
Seedbank.seeds_root = seedbank_root
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cnfs-storage
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1.alpha
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Robert Roach
|
|
8
|
+
- Rui Baltazar
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2020-01-23 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: aws-sdk-sqs
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - "~>"
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: 1.23.1
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - "~>"
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: 1.23.1
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: cnfs-core
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - '='
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: 0.0.1alpha
|
|
35
|
+
type: :runtime
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - '='
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: 0.0.1alpha
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: cnfs_sdk
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - '='
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: 0.0.1alpha
|
|
49
|
+
type: :runtime
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - '='
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: 0.0.1alpha
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: net-sftp
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - "~>"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: 2.1.2
|
|
63
|
+
type: :runtime
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - "~>"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 2.1.2
|
|
70
|
+
- !ruby/object:Gem::Dependency
|
|
71
|
+
name: rails
|
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - "~>"
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 6.0.2.1
|
|
77
|
+
type: :runtime
|
|
78
|
+
prerelease: false
|
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - "~>"
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: 6.0.2.1
|
|
84
|
+
- !ruby/object:Gem::Dependency
|
|
85
|
+
name: shoryuken
|
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - "~>"
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: 5.0.2
|
|
91
|
+
type: :runtime
|
|
92
|
+
prerelease: false
|
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - "~>"
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: 5.0.2
|
|
98
|
+
description: Processes CSV files, notifies other services when new files are available
|
|
99
|
+
email:
|
|
100
|
+
- rjayroach@gmail.com
|
|
101
|
+
- rui.p.baltazar@gmail.com
|
|
102
|
+
executables: []
|
|
103
|
+
extensions: []
|
|
104
|
+
extra_rdoc_files: []
|
|
105
|
+
files:
|
|
106
|
+
- MIT-LICENSE
|
|
107
|
+
- README.md
|
|
108
|
+
- Rakefile
|
|
109
|
+
- app/controllers/column_maps_controller.rb
|
|
110
|
+
- app/controllers/concerns/creatable_attachment.rb
|
|
111
|
+
- app/controllers/documents_controller.rb
|
|
112
|
+
- app/controllers/images_controller.rb
|
|
113
|
+
- app/controllers/reports_controller.rb
|
|
114
|
+
- app/controllers/storage/application_controller.rb
|
|
115
|
+
- app/controllers/transfer_maps_controller.rb
|
|
116
|
+
- app/jobs/platform_event_processor.rb
|
|
117
|
+
- app/jobs/storage/application_job.rb
|
|
118
|
+
- app/models/column_map.rb
|
|
119
|
+
- app/models/concerns/has_attachment.rb
|
|
120
|
+
- app/models/document.rb
|
|
121
|
+
- app/models/image.rb
|
|
122
|
+
- app/models/report.rb
|
|
123
|
+
- app/models/sftp_file.rb
|
|
124
|
+
- app/models/storage/application_record.rb
|
|
125
|
+
- app/models/tenant.rb
|
|
126
|
+
- app/models/transfer_map.rb
|
|
127
|
+
- app/operations/iam_public_key_process.rb
|
|
128
|
+
- app/policies/column_map_policy.rb
|
|
129
|
+
- app/policies/document_policy.rb
|
|
130
|
+
- app/policies/image_policy.rb
|
|
131
|
+
- app/policies/report_policy.rb
|
|
132
|
+
- app/policies/storage/application_policy.rb
|
|
133
|
+
- app/policies/transfer_map_policy.rb
|
|
134
|
+
- app/resources/column_map_resource.rb
|
|
135
|
+
- app/resources/document_resource.rb
|
|
136
|
+
- app/resources/image_resource.rb
|
|
137
|
+
- app/resources/report_resource.rb
|
|
138
|
+
- app/resources/storage/application_resource.rb
|
|
139
|
+
- app/resources/transfer_map_resource.rb
|
|
140
|
+
- app/workers/aws/storage_worker.rb
|
|
141
|
+
- config/environment.rb
|
|
142
|
+
- config/routes.rb
|
|
143
|
+
- config/settings.yml
|
|
144
|
+
- config/shoryuken.yml
|
|
145
|
+
- config/sidekiq.yml
|
|
146
|
+
- config/spring.rb
|
|
147
|
+
- db/migrate/20190609160652_create_transfer_maps.rb
|
|
148
|
+
- db/migrate/20190609160743_create_column_maps.rb
|
|
149
|
+
- db/migrate/20190609161139_create_documents.rb
|
|
150
|
+
- db/migrate/20190927024852_create_images.rb
|
|
151
|
+
- db/migrate/20190927074408_create_reports.rb
|
|
152
|
+
- db/migrate/20190929005520_create_sftp_files.rb
|
|
153
|
+
- db/migrate/20191220075835_add_count_to_document.rb
|
|
154
|
+
- db/seeds/development/data.seeds.rb
|
|
155
|
+
- db/seeds/development/tenants.seeds.rb
|
|
156
|
+
- lib/ros/storage.rb
|
|
157
|
+
- lib/ros/storage/engine.rb
|
|
158
|
+
- lib/ros/storage/version.rb
|
|
159
|
+
- lib/tasks/ros/storage_tasks.rake
|
|
160
|
+
homepage: http://guides.rails-on-services.org/
|
|
161
|
+
licenses:
|
|
162
|
+
- MIT
|
|
163
|
+
metadata: {}
|
|
164
|
+
post_install_message:
|
|
165
|
+
rdoc_options: []
|
|
166
|
+
require_paths:
|
|
167
|
+
- lib
|
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - ">"
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: 2.6.0
|
|
173
|
+
- - "<"
|
|
174
|
+
- !ruby/object:Gem::Version
|
|
175
|
+
version: '2.7'
|
|
176
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - ">"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: 1.3.1
|
|
181
|
+
requirements: []
|
|
182
|
+
rubygems_version: 3.0.3
|
|
183
|
+
signing_key:
|
|
184
|
+
specification_version: 4
|
|
185
|
+
summary: Manages uploads and downloads of files via UI and SFTP
|
|
186
|
+
test_files: []
|