dyno_scp 0.3.1 → 0.3.2
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 +4 -4
- data/.gitignore +1 -0
- data/LICENSE +21 -0
- data/README.md +77 -0
- data/Rakefile +13 -0
- data/app/controllers/dyno_scp/files_controller.rb +82 -0
- data/app/views/dyno_scp/files/_styles.html.erb +122 -0
- data/app/views/dyno_scp/files/index.html.erb +36 -0
- data/app/views/dyno_scp/files/new.html.erb +26 -0
- data/config/routes.rb +9 -0
- data/dyno_scp.gemspec +30 -0
- data/lib/dyno_scp/engine.rb +7 -0
- data/lib/dyno_scp/version.rb +3 -0
- data/lib/dyno_scp.rb +24 -0
- metadata +15 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e36a1fef7a2a5b75abab84888c57ac803b1d79443b98d256189d0d8411336b61
|
4
|
+
data.tar.gz: b9025e32e5c858a089582dfce98c913cd69bedd206f6a538745a5e9abecc1675
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7afebe8560732ad22932e41bd4f0cd0a12a60706f2bb3f83753cfeb466b9cabc86547110c975fd2eb8bf7f1fc63e71d80601238117673802535a975633d0b53f
|
7
|
+
data.tar.gz: df969cc70c84689f27dd54b4c7bcc92941ea327b81a9887856e50d4526dd992e969afea7e3fc0ba409b8468504919871daec2ce9e76dcd03ae59ba49695876a1
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Andrew Merritt
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Dyno Scp
|
2
|
+
|
3
|
+
Heroku doesnt allow you to scp a file up to their server. This gem creates a
|
4
|
+
barebones file upload mechanism that dumps a file uploaded into the public
|
5
|
+
directory. Heroku automatically cleans that directory regularly so we dont have
|
6
|
+
to worry about files lingering around.
|
7
|
+
|
8
|
+
## Contents
|
9
|
+
|
10
|
+
- [Getting Started](#getting-started)
|
11
|
+
- [Tips](#tips)
|
12
|
+
- [Uploading A File](#uploading-a-file)
|
13
|
+
- [Accessing Uploaded Files](#accessing-uploaded-files)
|
14
|
+
|
15
|
+
## Getting Started
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem "dyno_scp", "~> <version>"
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Mount the engine in your routes file:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
mount DynoScp::Engine, at: "/dyno_scp"
|
31
|
+
```
|
32
|
+
|
33
|
+
### Tips
|
34
|
+
|
35
|
+
Heroku runs processes in their own separate dynos. If you add a file in your
|
36
|
+
console session and then exit and re-enter your file will be gone. This becomes
|
37
|
+
and issue when using this tool because when you upload a file it is placed in
|
38
|
+
the web dyno which you can't directly access.
|
39
|
+
|
40
|
+
See [Accessing Uploaded Files](#accessing-uploaded-files) for retrieval steps.
|
41
|
+
|
42
|
+
## Uploading A File
|
43
|
+
|
44
|
+
##### Note: The upload page requires the user to have an office:sys_admin role
|
45
|
+
After mounting the engine, you can navigate to `#{host}/dyno_scp` to see the upload page.
|
46
|
+
|
47
|
+
Simply choose the file you wish to upload, then press the "upload" button. You
|
48
|
+
will see a confirmation message once the file has been uploaded.
|
49
|
+
|
50
|
+
Files are placed in the `/public` folder, so use this with caution. They will
|
51
|
+
be publicly accessible to anyone who finds out they exist.
|
52
|
+
|
53
|
+
In Heroku, these files are cleared out every 24 hours automatically. In other
|
54
|
+
hosting schemes, there may not be automated cleanup of the public folder.
|
55
|
+
|
56
|
+
## Accessing Uploaded Files
|
57
|
+
|
58
|
+
The main use case for this tool is to upload a file to a heroku hosted site
|
59
|
+
for usage in some type of data load or backfill task.
|
60
|
+
|
61
|
+
After setting up open a bash console on the heroku app.
|
62
|
+
```bash
|
63
|
+
heroku run bash --app my-heroku-app
|
64
|
+
```
|
65
|
+
|
66
|
+
To use files after they've been uploaded with this tool, you need to retreive
|
67
|
+
the file inside the bash terminal from the web dyno where it was uploaded.
|
68
|
+
|
69
|
+
Using `curl`
|
70
|
+
```bash
|
71
|
+
curl https://my-heroku-app.herokuapp.com/my_uploaded_file.csv -o my_uploaded_file.csv
|
72
|
+
```
|
73
|
+
|
74
|
+
Using `wget`
|
75
|
+
```bash
|
76
|
+
wget https://my-heroku-app.herokuapp.com/my_uploaded_file.csv my_uploaded_file.csv
|
77
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
9
|
+
t.verbose = true
|
10
|
+
t.warning = false
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: :test
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DynoScp
|
4
|
+
class FilesController < ::ActionController::Base
|
5
|
+
skip_after_action :verify_authorized, raise: false
|
6
|
+
before_action :ensure_public_folder_exists
|
7
|
+
|
8
|
+
layout false if respond_to?(:layout)
|
9
|
+
|
10
|
+
def index
|
11
|
+
@files = fetch_public_files
|
12
|
+
end
|
13
|
+
|
14
|
+
def new; end
|
15
|
+
|
16
|
+
def create
|
17
|
+
file = params[:file]
|
18
|
+
|
19
|
+
if file.nil?
|
20
|
+
redirect_to new_file_path, alert: "No file selected"
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
filename = resolve_name_collision(file.original_filename)
|
25
|
+
|
26
|
+
File.open(Rails.root.join("#{::DynoScp.folder_path}/#{filename}"), "wb") do |f|
|
27
|
+
f.write file.read
|
28
|
+
end
|
29
|
+
|
30
|
+
redirect_to new_file_path, notice: "#{filename} uploaded"
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroy
|
34
|
+
file_path = Rails.root.join("#{::DynoScp.folder_path}/#{params[:file_name]}")
|
35
|
+
if File.exist?(file_path)
|
36
|
+
File.delete(file_path)
|
37
|
+
redirect_to files_path, notice: "#{params[:file_name]} deleted"
|
38
|
+
else
|
39
|
+
redirect_to files_path, notice: "File not found"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def ensure_public_folder_exists
|
46
|
+
FileUtils.mkdir_p("#{::DynoScp.folder_path}") unless Dir.exist?(::DynoScp.folder_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def fetch_public_files
|
50
|
+
files = Dir.glob("#{::DynoScp.folder_path}/**/*").select { |file| File.file?(file) }
|
51
|
+
|
52
|
+
files.map do |file|
|
53
|
+
{
|
54
|
+
name: File.basename(file),
|
55
|
+
relative_path: "/#{::DynoScp.folder}/#{File.basename(file)}",
|
56
|
+
path: Rails.root.join(file).to_s,
|
57
|
+
size: File.size(file),
|
58
|
+
created_at: File.ctime(file).to_fs(:long)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def resolve_name_collision(filename)
|
64
|
+
return filename unless File.exist?("#{save_path}/#{filename}")
|
65
|
+
|
66
|
+
basename = File.basename(filename, ".*")
|
67
|
+
extension = File.extname(filename)
|
68
|
+
counter = 1
|
69
|
+
|
70
|
+
# Loop until we find a file name that doesn't exist
|
71
|
+
while File.exist?("#{save_path}/#{basename}(#{counter})#{extension}")
|
72
|
+
counter += 1
|
73
|
+
end
|
74
|
+
|
75
|
+
"#{basename}(#{counter})#{extension}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def save_path
|
79
|
+
Rails.root.join("#{::DynoScp.folder_path}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
<style>
|
2
|
+
.alert {
|
3
|
+
color: #690906;
|
4
|
+
padding: 10px 5px;
|
5
|
+
border: 2px solid #EB918F;
|
6
|
+
background-color: #FFE3E2;
|
7
|
+
border-radius: 4px;
|
8
|
+
display: flex;
|
9
|
+
justify-content: center;
|
10
|
+
}
|
11
|
+
.btn-primary {
|
12
|
+
background-color: #ffffff;
|
13
|
+
color: #2B2B2B;
|
14
|
+
}
|
15
|
+
.btn-primary:hover {
|
16
|
+
background-color: #E5E7EB;
|
17
|
+
}
|
18
|
+
.btn-danger {
|
19
|
+
background-color: #FFE3E2;
|
20
|
+
color: #690906;
|
21
|
+
border-color: #EB918F;
|
22
|
+
}
|
23
|
+
.btn-danger:hover {
|
24
|
+
color: #FFE3E2;
|
25
|
+
background-color: #D26663;
|
26
|
+
}
|
27
|
+
.btn {
|
28
|
+
margin: 4px;
|
29
|
+
text-decoration: none;
|
30
|
+
border: 2px solid #A5A7AB;
|
31
|
+
border-radius: 5px;
|
32
|
+
display: inline-block;
|
33
|
+
padding: 10px 20px;
|
34
|
+
font-size: 16px;
|
35
|
+
cursor: default;
|
36
|
+
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.2);
|
37
|
+
transition: background-color 0.3s ease; /* Smooth transition */
|
38
|
+
}
|
39
|
+
.btn:hover {
|
40
|
+
cursor: pointer;
|
41
|
+
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.2);
|
42
|
+
}
|
43
|
+
.card {
|
44
|
+
background: #fff;
|
45
|
+
padding: 20px;
|
46
|
+
max-width: 500px;
|
47
|
+
max-height: 400px;
|
48
|
+
text-align: left;
|
49
|
+
font-family: Arial;
|
50
|
+
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
|
51
|
+
transition: 0.3s;
|
52
|
+
border-radius: 2px;
|
53
|
+
}
|
54
|
+
.card:hover {
|
55
|
+
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
|
56
|
+
}
|
57
|
+
.container {
|
58
|
+
padding-left: 20px;
|
59
|
+
padding-right: 20px;
|
60
|
+
width: 100%;
|
61
|
+
height: 100%;
|
62
|
+
max-width: 540px;
|
63
|
+
padding-top: 80px;
|
64
|
+
display: flex;
|
65
|
+
flex-direction: column;
|
66
|
+
justify-content: start;
|
67
|
+
margin-left: auto;
|
68
|
+
margin-right: auto;
|
69
|
+
}
|
70
|
+
.flex {
|
71
|
+
display: flex;
|
72
|
+
}
|
73
|
+
.flex-column {
|
74
|
+
flex-direction: column;
|
75
|
+
}
|
76
|
+
.flex-row {
|
77
|
+
flex-direction: row;
|
78
|
+
}
|
79
|
+
.items-base {
|
80
|
+
align-items: baseline;
|
81
|
+
}
|
82
|
+
.items-end {
|
83
|
+
align-items: flex-end;
|
84
|
+
}
|
85
|
+
.justify-center {
|
86
|
+
justify-content: center;
|
87
|
+
}
|
88
|
+
.justify-end {
|
89
|
+
justify-content: flex-end;
|
90
|
+
}
|
91
|
+
.font-danger {
|
92
|
+
color: #8B0000;
|
93
|
+
}
|
94
|
+
.mb-4 {
|
95
|
+
margin-bottom: 4px;
|
96
|
+
}
|
97
|
+
.mb-20 {
|
98
|
+
margin-bottom: 20px;
|
99
|
+
}
|
100
|
+
.ml-20 {
|
101
|
+
margin-left: 20px;
|
102
|
+
}
|
103
|
+
.notice {
|
104
|
+
color: #055405;
|
105
|
+
padding: 10px 5px;
|
106
|
+
border: 2px solid #72BC72;
|
107
|
+
background-color: #CCE7CC;
|
108
|
+
border-radius: 4px;
|
109
|
+
display: flex;
|
110
|
+
justify-content: center;
|
111
|
+
}
|
112
|
+
.right-align {
|
113
|
+
margin-left: auto;
|
114
|
+
}
|
115
|
+
.row {
|
116
|
+
display: flex;
|
117
|
+
justify-content: space-between;
|
118
|
+
}
|
119
|
+
.text-center {
|
120
|
+
text-align: center;
|
121
|
+
}
|
122
|
+
</style>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<%= render "styles" %>
|
2
|
+
|
3
|
+
|
4
|
+
<div class="container">
|
5
|
+
<div class="mb-20">
|
6
|
+
<%- if notice %>
|
7
|
+
<div class="notice"><%= notice %></div>
|
8
|
+
<% elsif alert %>
|
9
|
+
<div class="alert font-danger"><%= alert %></div>
|
10
|
+
<% end %>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<%= button_to "Upload File", new_file_path, method: :get, class: "btn btn-primary right-align"%>
|
14
|
+
|
15
|
+
<%- if @files.count.zero? %>
|
16
|
+
<div class="card-holder">
|
17
|
+
<div class="card text-center">
|
18
|
+
<h3 class="font-danger">No files uploaded yet</h3>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
<%- else %>
|
22
|
+
<div class="card-holder">
|
23
|
+
<% @files.each do |file| %>
|
24
|
+
<div class="card mb-20">
|
25
|
+
<h3><%= file[:name] %></h3>
|
26
|
+
<p><%= "Size: #{file[:size]} B" %></p>
|
27
|
+
<p><%= "Uploaded: #{file[:created_at]}" %></p>
|
28
|
+
<div class="flex flex-row justify-end items-base">
|
29
|
+
<%= link_to "Download", file[:relative_path], target: "_blank", download: file[:name], class: "mb-4" %>
|
30
|
+
<%= button_to "Delete", file_path(file_name: file[:name]), method: :delete, class: "btn btn-danger ml-20" %>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
<% end %>
|
34
|
+
</div>
|
35
|
+
<%- end %>
|
36
|
+
</div>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<%= render "styles" %>
|
2
|
+
|
3
|
+
<div class="container">
|
4
|
+
<div class="mb-20">
|
5
|
+
<%- if notice %>
|
6
|
+
<div class="notice"><%= notice %></div>
|
7
|
+
<%- elsif alert %>
|
8
|
+
<div class="alert font-danger"><%= alert %></div>
|
9
|
+
<% end %>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
|
13
|
+
<%= button_to "View Files", files_path, method: :get, class: "btn btn-primary right-align"%>
|
14
|
+
|
15
|
+
<div class="card-holder">
|
16
|
+
<div class="card">
|
17
|
+
<h3>File Upload</h3>
|
18
|
+
<%= form_with url: files_path, method: :post do |f| %>
|
19
|
+
<div class="row">
|
20
|
+
<%= f.file_field :file, class: "btn btn-primary" %>
|
21
|
+
<%= f.submit "Upload", class: "btn btn-primary" %>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</div>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
DynoScp::Engine.routes.draw do
|
4
|
+
# Only allow access if the engine was mounted in the /dyno_scp namespace
|
5
|
+
constraints(->(req) { req.fullpath.split("/").reject(&:blank?).first == "dyno_scp" }) do
|
6
|
+
resources :files, only: %i(index new create destroy), param: :file_name, constraints: { file_name: /.*/ }
|
7
|
+
root "files#index"
|
8
|
+
end
|
9
|
+
end
|
data/dyno_scp.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require_relative "./lib/dyno_scp/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "dyno_scp"
|
7
|
+
spec.version = DynoScp::VERSION
|
8
|
+
spec.authors = ["Andrew Merritt"]
|
9
|
+
spec.email = ["andrew.w.merritt@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Dyno SCP"
|
12
|
+
spec.description = "Allows devs to scp files to server for Heroku apps"
|
13
|
+
spec.homepage = "https://github.com/amerritt14/dyno_scp"
|
14
|
+
|
15
|
+
# Specify which files should be added to the gem when it is released.
|
16
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|features)/}) }
|
19
|
+
end
|
20
|
+
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency "rails"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", ">= 1.16"
|
26
|
+
spec.add_development_dependency "minitest", ">= 5.11"
|
27
|
+
spec.add_development_dependency "pry"
|
28
|
+
spec.add_development_dependency "rake", ">= 12.3.3"
|
29
|
+
spec.add_development_dependency "rubocop"
|
30
|
+
end
|
data/lib/dyno_scp.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dyno_scp/version"
|
4
|
+
require "dyno_scp/engine"
|
5
|
+
|
6
|
+
module DynoScp
|
7
|
+
FOLDER = "dyno_scp"
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def folder
|
11
|
+
FOLDER
|
12
|
+
end
|
13
|
+
|
14
|
+
def folder_path
|
15
|
+
"public/#{FOLDER}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def dyno_scp_config
|
22
|
+
Rails.application.config_for("dyno_scp")
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dyno_scp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Merritt
|
@@ -100,7 +100,20 @@ email:
|
|
100
100
|
executables: []
|
101
101
|
extensions: []
|
102
102
|
extra_rdoc_files: []
|
103
|
-
files:
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- LICENSE
|
106
|
+
- README.md
|
107
|
+
- Rakefile
|
108
|
+
- app/controllers/dyno_scp/files_controller.rb
|
109
|
+
- app/views/dyno_scp/files/_styles.html.erb
|
110
|
+
- app/views/dyno_scp/files/index.html.erb
|
111
|
+
- app/views/dyno_scp/files/new.html.erb
|
112
|
+
- config/routes.rb
|
113
|
+
- dyno_scp.gemspec
|
114
|
+
- lib/dyno_scp.rb
|
115
|
+
- lib/dyno_scp/engine.rb
|
116
|
+
- lib/dyno_scp/version.rb
|
104
117
|
homepage: https://github.com/amerritt14/dyno_scp
|
105
118
|
licenses: []
|
106
119
|
metadata: {}
|