Passing jpg file directly on from backend server - ruby

I'm uploading an image file from my app via JSON to my ruby backend hosted by Heroku. It is then supposed to be passed to stripe via their gem via a path to my uploaded image. Is it possible to just pass it the tempfile? The stripe docs say to do:
Stripe::FileUpload.create(
:purpose => 'dispute_evidence',
:file => File.new('/path/to/a/file.jpg')
)
my backend post looks like:
post '/account/id' do
tempfile = params[:file][:tempfile]
filename = params[:file][:filename]
path = "#{tempfile.path}/#{filename}"
p path --> "/tmp/RackMultipart20170112-4-fgtv2n/photoID.jpg"
begin
file = Stripe::FileUpload.create({
:purpose => params[:purpose],
:file => File.new(path)
},
{
:stripe_account => params[:stripe_account]
}
)
rescue Stripe::StripeError => e
status 402
return "Error saving verification id to account: #{e.message}"
end
status 200
return file.to_json
end
but when running i get:
Errno::ENOENT - No such file or directory # rb_sysopen - /tmp/RackMultipart20170112-4-1qmr03y/photoID.jpg:
i cant figure out what im doing wrong. can any one help or suggest a better option?

You are mistaking tempfile for a directory. tempfile is actually your temporary file, so pass it instead of path (that you don't need any more).

Related

Sinatra send_file gives 404 error when trying to store the file

I am trying to download the file by simulating using postman
Response
127.0.0.1 - - [18/Jan/2021:03:54:47 -0800] "POST / HTTP/1.1" 404 - 4.4450
Command
send_file "#{filename}", :disposition=> "attachment", :filename => filename, :type => 'application/octet-stream'
post '/' do
fileSize = env['CONTENT_LENGTH'].to_i/1048576.0 # Converting to MB
if params.has_key?(:file) && fileSize <= 1
filename = params['file']["filename"]
send_file "#{filename}", :disposition=> "attachment", :filename => filename, :type => 'application/octet-stream'
puts ".......................FILE STORED........................."
else
puts "....................FILE NOT VALID........................."
end
end
After seeing your replys to my comments I can say the reason why this is not working is because the send_file command is not being supplied with the correct argments. According to these docs: https://apidock.com/rails/v2.3.8/ActionController/Streaming/send_file - you must attach the file path as the first parameter and not just the file name.
Also, the location of the file (your downloads folder) may cause other problems. The file should be available from within your project directory somewhere, preferably in the /public folder.
To summarise:
Move the file to the /public directory within your project.
Prepend the file name with the path to where the file will be stored.

Delete a file using Ruby SFTP `remove` and `remove!`

I am trying to old delete files from an FTP using Ruby net/sftp, but I keep getting an error saying the file does not exist.
:error=>"Net::SFTP::StatusException (2, \"no such file\")"
I can manually delete files when logging in using the same creds, so I know I have permission.
require 'net/sftp'
ftp = Net::SFTP.start(#ftp_url, #ftp_user, :password => #ftp_pwd)
ftp.dir.entries('somePath').each do |entry|
begin
age_days = (Time.now.to_i - entry.attributes.atime) / 86400
if(age_days > ftp_max_file_age_days)
ftp.remove!(entry.name)
end
rescue Exception => e
# log error here
end
end
I prefer remove! so everything happens synchronously in this case, but I have also tried remove.
I also tried giving it the full path of the file instead of just the entry name (like 'somePath' + entry.name instead of just entry.name). I was thinking perhaps it was because I needed to change the working directory, which apparently net/sftp does not allow.
Thanks in advance!
Check if entry is a directory if yes then use ftp.rmdir. like below -
require 'net/sftp'
ftp = Net::SFTP.start(#ftp_url, #ftp_user, :password => #ftp_pwd)
ftp.dir.entries('somePath').each do |entry|
begin
age_days = (Time.now.to_i - entry.attributes.atime) / 86400
if(age_days > ftp_max_file_age_days)
if File.directory?(entry.name)
ftp.rmdir(entry.name)
else
ftp.remove!(entry.name)
end
end
rescue Exception => e
# log error here
end
end
We were eventually able to delete files using the remove method (instead of remove!.) We made a small change to the way we provide the password.
We confirmed that permissions on the FTP did not change, so I think using non_interactive: true may have been the trick.
require 'net/sftp'
def self.delete_report(endpoint, username, password, report_filename)
SSH_OPTIONS = { non_interactive: true }.freeze
report_filename_base = File.basename(report_filename, '.*')
Net::SFTP.start(endpoint, username, SSH_OPTIONS.merge(password: password)) do |sftp|
sftp.remove(report_filename)
sftp.remove("#{report_filename_base}.fin")
sftp.remove("processed/#{report_filename}")
sftp.remove("processed/#{report_filename_base}.fin")
sftp.remove("failed/#{report_filename}")
sftp.remove("failed/#{report_filename_base}.fin")
sftp.remove("failed/#{report_filename_base}.info")
end
I still don't fully understand why the same method did not work before, but we're able to delete files in subfolders too, as shown in this example.

Sinatra list of image files works on localhost but not on the test server

The code below gets a list of images from the public/images directory, creates an array of hashes and converts it to JSON then returns to the caller.
The code works perfectly on the local host - I get the list of image names as needed.
I then uploaded the code to my VPS and ran it on the same environment, running on thin, and nothing is returned at all. No matter what I change, either the path or method for getting filenames, like using glob instead of just Dir, nothing works that I tried.
Here is the code in the route I call from the client-side JavaScript using Ajax:
# get all images
get '/debug/posts/images/' do
puts '>> debug > posts > images > get'
all_images = Array.new
# build substitute prefix path
uri = URI(request.url)
prefix ='http://' + uri.host
if request.port
prefix += ':' + request.port.to_s
end
prefix += '/content/'
begin
content_type :json
# get list of images
pics = Dir['public/images/*']
pics.map { |pic|
# build hash for use with tinyMCE
pic.split('/')
pic_hash = {:title => File.basename(pic).to_s, :value => prefix + File.basename(pic).to_s}
all_images.push(pic_hash)
}
# convert to json
pic_json = JSON.generate(all_images)
body(pic_json)
rescue Sequel::Error => e
puts e.message
status(400).to_json
end
end
I get an array of values back running on the localhost:
[ {title: "bear-love.jpg"value: "/content/bear-love.jpg"}, {title: "bear-love2.jpg"value: "/content/bear-love2.jpg"}...]
I get an empty array from the VPS:
[]
Check that the server is being run as a user who has permission to read and execute public/images
verify that public/images exists
verify that public/images is where you think it is.
Turns out that it's easier to get the current path on the server and simply get a list of the image filenames I needed that way and just add my prefix path for use from the browser.

How can I download multiple .xlsx files using axlsx gem?

Hi I'm having trouble downloading multiple files with axlsx. The problem is I'm sending an array of Id's to the controller and asking it to download the report using the render command. It raises an AbstractController::DoubleRenderError. I was thinking of overriding the error but realized it's a bad idea, I don't know what else to do... Any suggestions? Thanks.
My controller code looks like this:
def download_report
params[:user_id].each do |user_id|
#report = Report.find_by(:user_id => user_id)
render :xlsx => "download_report", :filename => "#{#report.user.last_name}.xlsx"
end
end
My axlsx template:
wb = xlsx_package.workbook
wb.add_worksheet(name: "Reports") do |sheet|
wb.styles do |s|
# template code
end
end
It is the built in expectation of Rails that you would call render once per request. And, the browser is going to expect one response per request. So, you are going to have to do something else!
You can use render_to_string, and combine the results into a zip file, serving that. See the bottom of this response.
Or, you could create a single spreadsheet and have each user's report show up on their own worksheet.
Or, on the client side, you could use javascript to request each spreadsheet and download each one separately.
The zip one would be something like this code, which uses render_to_string, rubyzip, and send_data:
def download_report
compressed_filestream = Zip::ZipOutputStream.write_buffer do |zos|
params[:user_id].each do |user_id|
#report = Report.find_by(:user_id => user_id)
content = render_to_string :xlsx => "download_report", :filename => "#{#report.user.last_name}.xlsx"
zos.put_next_entry("user_#{user_id}.xlsx")
zos.print content
end
end
compressed_filestream.rewind
send_data compressed_filestream.read, :filename => 'download_report.zip', :type => "application/zip"
end
Axlsx requires rubyzip, so you should have it already. And you probably want to lookup each user and use their name for the spreadsheet, unless you have it otherwise.

Sinatra, progress bar in upload form

I'm developing a Sinatra app that consists of an upload form, with a progress bar indicating how much of the upload has completed.
The process, as described by ryan dahl, is the following:
HTTP upload progress bars are rather obfuscated- they typically involve a process running on the server keeping track of the size of the tempfile that the HTTP server is writing to, then on the client side an AJAX call is made every couple seconds to the server during the upload to ask for the progress of the upload.
Every upload has a random session-id, and to keep track of the association i employ a class variable in my app (i know, that's horrible -- if you've got better ideas, please tell me)
configure do
##assoc = {}
end
I have a POST route for the upload, and a GET one for the AJAX polling.
Inside the POST route i save the association of session-id, Tempfile, and total size.
post '/files' do
tmp = params[:file][:tempfile]
# from here on, ##assoc[#sid] should have a value, even in other routes
##assoc[#sid] = { :file => tmp, :size => env['CONTENT_LENGTH'] }
File.open("#{options.filesdir}/#{filename}", 'w+') do |file|
file << tmp.read
end
end
In the GET route, i calculate the percentage based on the Tempfile's current size:
get '/status/:sid' do
h = ##assoc[params[:sid]]
unless h.nil?
percentage = (h[:file].size / h[:size].to_f) * 100
"#{percentage}%"
else
"0%"
end
end
The problem is that until the POST request hasn't completed (i.e., after it has read all of the Tempfile) the h.nil? returns true, which doesn't really make sense as I've just assigned ##assoc[#sid] a value in the other route.
So, what am I missing here?
EDIT: I've tried
set :reload, false
set :environment, :production
config { ##assoc ||= {} }
I also tried throwing a relational db at it (SQLite with DataMapper)
Neither worked.
I think i got what the problem is:
tmp = params[:file][:tempfile] doesn't return until the file has been fully received.
##assoc[#sid] = { :file => tmp, :size => env['CONTENT_LENGTH'] }
should be
##assoc[params[:sid]] = { :file => tmp, :size => env['CONTENT_LENGTH'] }

Resources