Skip to content

Instantly share code, notes, and snippets.

@bdevel
Last active July 28, 2016 22:03
Show Gist options
  • Save bdevel/eb186a2d063bfdeb6814a0e6248f9a89 to your computer and use it in GitHub Desktop.
Save bdevel/eb186a2d063bfdeb6814a0e6248f9a89 to your computer and use it in GitHub Desktop.
Rails controller for storing and fetching files from Postgres Large Objects (LO)
require 'digest'
class Api::V2::FileStorageController < ApplicationController
skip_before_filter :authorize, :only => [:save, :serve]
before_filter :authorize_admin_api_token, :only => [:save]
def save
if params[:contents].is_a?(ActionDispatch::Http::UploadedFile)
params[:contents].close(false) # close and don't delete so we can import it
tmp_file = params[:contents]
else
raise "No upload to process."
end
uploaded_file = nil
sha = Digest::SHA512.file tmp_file.path
sha.hexdigest
UploadedFile.connection.transaction do
uploaded_file = UploadedFile.find_or_initialize_by(filename: params[:filename])
if uploaded_file && uploaded_file.file_oid
# Remove old file
UploadedFile.connection.execute "SELECT LO_UNLINK('#{uploaded_file.file_oid}')"
end
res = UploadedFile.connection.execute "SELECT LO_IMPORT('#{tmp_file.path}') AS file_oid"
uploaded_file.file_oid = res[0]["file_oid"].to_i
uploaded_file.checksum = sha.hexdigest
uploaded_file.headers = params[:headers]
uploaded_file.permissions = params[:permissions] || 'protected'
uploaded_file.save!
end
File.unlink tmp_file.path
render :json => uploaded_file.to_json, status: :created
end
def serve
file = UploadedFile.find_by(filename: params[:filename])
if file.nil?
render :text => "File not found.", content_type: "text/plain", status: 404
return
end
if !file.public?
authorize()
return unless signed_in?
end
# Make sure if file changes that it will bust the cache
tmp_path = "#{Rails.root}/tmp/uploaded_file_#{file.file_oid}_#{file.updated_at.to_i}"
if !File.exist?(tmp_path)
UploadedFile.connection.execute "SELECT LO_EXPORT(#{file.file_oid}, '#{tmp_path}')"
end
send_file(tmp_path, type: file.mime_type, disposition: nil)
file.headers.each do |k, v|
response.headers[k] = v
end unless file.headers.nil?
end
end
class UploadedFile < ActiveRecord::Base
store_accessor :headers
def extension
filename.match(/\.[a-zA-Z]+$/).to_a.first.to_s.downcase
end
def mime_type
Rack::Mime.mime_type(extension)
end
def as_json(*args)
json = super(*args)
json["mimeType"] = mime_type
json
end
def public?
permissions == 'public'
end
end
class CreateUploadedFiles < ActiveRecord::Migration
def change
create_table :uploaded_files do |t|
t.string :filename
t.string :checksum
t.string :permissions
t.integer :file_oid
t.hstore :headers
t.timestamps
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment