require "net/http"
require "json"
require "uri"
require "digest"
require "base64"
class ScholarlyFileUploader
BASE_URL = "https://api.scholarlysoftware.com"
def initialize(api_token)
@api_token = api_token
end
# Step 1: Initiate the direct upload
def initiate_upload(folder_id:, file_path:, content_type: "application/octet-stream")
file_content = File.binread(file_path)
file_name = File.basename(file_path)
file_size = file_content.bytesize
checksum = Base64.strict_encode64(Digest::MD5.digest(file_content))
uri = URI("#{BASE_URL}/api/v1/folders/#{folder_id}/file_upload_requests")
request_body = {
data: {
type: "file",
attributes: {
name: file_name,
size: file_size,
checksum: checksum,
content_type: content_type
}
}
}
response = make_request(:post, uri, request_body)
{
file_id: response.dig("data", "id"),
upload_url: response.dig("meta", "direct_upload", "url"),
upload_headers: response.dig("meta", "direct_upload", "headers"),
blob_signed_id: response.dig("meta", "direct_upload", "blob_signed_id"),
file_content: file_content
}
end
# Step 2: Upload file directly to S3
def upload_to_s3(upload_url:, upload_headers:, file_content:)
uri = URI(upload_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Put.new(uri)
upload_headers.each { |key, value| request[key] = value }
request["Content-Length"] = file_content.bytesize.to_s
request.body = file_content
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
raise "S3 upload failed: #{response.code} - #{response.body}"
end
true
end
# Step 3: Confirm the upload
def confirm_upload(file_id:, blob_signed_id:)
uri = URI("#{BASE_URL}/api/v1/file_upload_requests/#{file_id}/confirm")
request_body = {blob_signed_id: blob_signed_id}
make_request(:post, uri, request_body)
end
# Convenience method to upload a file in one call
def upload_file(folder_id:, file_path:, content_type: "application/octet-stream")
# Step 1: Initiate
upload_info = initiate_upload(
folder_id: folder_id,
file_path: file_path,
content_type: content_type
)
puts "Initiated upload for file ID: #{upload_info[:file_id]}"
# Step 2: Upload to S3
upload_to_s3(
upload_url: upload_info[:upload_url],
upload_headers: upload_info[:upload_headers],
file_content: upload_info[:file_content]
)
puts "Uploaded to S3 successfully"
# Step 3: Confirm
result = confirm_upload(
file_id: upload_info[:file_id],
blob_signed_id: upload_info[:blob_signed_id]
)
puts "Upload confirmed. File state: #{result.dig("data", "attributes", "state")}"
result
end
private
def make_request(method, uri, body = nil)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = case method
when :post then Net::HTTP::Post.new(uri)
when :get then Net::HTTP::Get.new(uri)
when :put then Net::HTTP::Put.new(uri)
end
request["Authorization"] = "Bearer #{@api_token}"
request["Content-Type"] = "application/vnd.api+json"
request["Accept"] = "application/vnd.api+json"
request.body = body.to_json if body
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
raise "API request failed: #{response.code} - #{response.body}"
end
JSON.parse(response.body)
end
end
# Usage example:
if __FILE__ == $0
api_token = ENV.fetch("SCHOLARLY_API_TOKEN")
folder_id = "uuid-of-the-folder"
file_path = "/path/to/your/file.md"
uploader = ScholarlyFileUploader.new(api_token)
begin
result = uploader.upload_file(
folder_id: folder_id,
file_path: file_path,
content_type: "text/plain"
)
puts "\nUpload complete!"
puts "File ID: #{result.dig("data", "id")}"
puts "File URL: #{result.dig("data", "attributes", "url")}"
puts "Download link: #{result.dig("data", "links", "download")}"
rescue => e
puts "Error: #{e.message}"
exit 1
end
end