Merge branch 'staging' into docs_cleanup
commit
27c730ea13
|
@ -21,3 +21,4 @@ public/system
|
|||
public/webpack
|
||||
public/webpack/*
|
||||
tmp
|
||||
public/direct_upload/temp/*.jpg
|
||||
|
|
|
@ -4,12 +4,11 @@ module Api
|
|||
# 2. POST the URL from step 1 (or any URL) to ImagesController#Create
|
||||
# 3. Image is transfered to the "trusted bucket".
|
||||
class ImagesController < Api::AbstractController
|
||||
BUCKET = ENV.fetch("GCS_BUCKET") { "YOU_MUST_CONFIG_GOOGLE_CLOUD_STORAGE" }
|
||||
KEY = ENV.fetch("GCS_KEY") { "YOU_MUST_CONFIG_GCS_KEY" }
|
||||
SECRET = ENV.fetch("GCS_ID") { "YOU_MUST_CONFIG_GCS_ID" }
|
||||
cattr_accessor :store_locally
|
||||
self.store_locally = !ENV["GCS_BUCKET"]
|
||||
|
||||
def create
|
||||
mutate Images::Create.run(raw_json, device: current_device)
|
||||
mutate Images::Create.run(raw_json, device: current_device)
|
||||
end
|
||||
|
||||
def index
|
||||
|
@ -28,51 +27,17 @@ module Api
|
|||
# Creates a "policy object" + meta data so that users may upload an image to
|
||||
# Google Cloud Storage.
|
||||
def storage_auth
|
||||
# Creates a 1 hour authorization for a user to upload an image file to a
|
||||
# Google Cloud Storage bucket.
|
||||
# You probably want to POST that URL to Images#Create after that.
|
||||
render json: {
|
||||
verb: "POST",
|
||||
url: "//storage.googleapis.com/#{BUCKET}/",
|
||||
form_data: {
|
||||
"key" => random_filename,
|
||||
"acl" => "public-read",
|
||||
"Content-Type" => "image/jpeg",
|
||||
"policy" => policy,
|
||||
"signature" => policy_signature,
|
||||
"GoogleAccessId" => KEY,
|
||||
"file" => "REPLACE_THIS_WITH_A_BINARY_JPEG_FILE"
|
||||
},
|
||||
instructions: "Send a 'from-data' request to the URL provided."\
|
||||
"Then POST the resulting URL as an 'attachment_url' "\
|
||||
"(json) to api/images/."
|
||||
}
|
||||
mutate policy_class.run
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# The image URL in the "untrusted bucket" in Google Cloud Storage
|
||||
def random_filename
|
||||
@range ||= "temp1/#{SecureRandom.uuid}.jpg"
|
||||
end
|
||||
|
||||
def policy
|
||||
@policy ||= Base64.encode64(
|
||||
{ 'expiration' => 1.hour.from_now.utc.xmlschema,
|
||||
'conditions' => [
|
||||
{ 'bucket' => BUCKET },
|
||||
{ 'key' => random_filename},
|
||||
{ 'acl' => 'public-read' },
|
||||
{ 'Content-Type' => "image/jpeg"},
|
||||
['content-length-range', 1, 7.megabytes]
|
||||
]}.to_json).gsub(/\n/, '')
|
||||
end
|
||||
|
||||
def policy_signature
|
||||
@policy_signature ||= Base64.encode64(
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'),
|
||||
SECRET,
|
||||
policy)).gsub("\n",'')
|
||||
def policy_class
|
||||
if ImagesController.store_locally
|
||||
Images::GeneratePolicy
|
||||
else
|
||||
Images::StubPolicy
|
||||
end
|
||||
end
|
||||
|
||||
def image
|
||||
|
|
|
@ -46,6 +46,16 @@ class DashboardController < ApplicationController
|
|||
render json: report
|
||||
end
|
||||
|
||||
# (for self hosted users) Direct image upload endpoint.
|
||||
# Do not use this if you use GCS- it will slow your app down.
|
||||
def direct_upload
|
||||
raise "No." unless Api::ImagesController.store_locally
|
||||
name = params.fetch(:key).split("/").last
|
||||
path = File.join("public", "direct_upload", "temp", name)
|
||||
File.open(path, "wb") { |f| f.write(params[:file]) }
|
||||
render json: ""
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_global_config
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
module Images
|
||||
class GeneratePolicy < Mutations::Command
|
||||
BUCKET = ENV.fetch("GCS_BUCKET") { "YOU_MUST_CONFIG_GOOGLE_CLOUD_STORAGE" }
|
||||
KEY = ENV.fetch("GCS_KEY") { "YOU_MUST_CONFIG_GCS_KEY" }
|
||||
SECRET = ENV.fetch("GCS_ID") { "YOU_MUST_CONFIG_GCS_ID" }
|
||||
|
||||
def execute
|
||||
{
|
||||
verb: "POST",
|
||||
url: "//storage.googleapis.com/#{BUCKET}/",
|
||||
form_data: {
|
||||
"key" => random_filename,
|
||||
"acl" => "public-read",
|
||||
"Content-Type" => "image/jpeg",
|
||||
"policy" => policy,
|
||||
"signature" => policy_signature,
|
||||
"GoogleAccessId" => KEY,
|
||||
"file" => "REPLACE_THIS_WITH_A_BINARY_JPEG_FILE"
|
||||
},
|
||||
instructions: "Send a 'from-data' request to the URL provided."\
|
||||
"Then POST the resulting URL as an 'attachment_url' "\
|
||||
"(json) to api/images/."
|
||||
}
|
||||
end
|
||||
private
|
||||
# The image URL in the "untrusted bucket" in Google Cloud Storage
|
||||
def random_filename
|
||||
@range ||= "temp1/#{SecureRandom.uuid}.jpg"
|
||||
end
|
||||
|
||||
def policy
|
||||
@policy ||= Base64.encode64(
|
||||
{ 'expiration' => 1.hour.from_now.utc.xmlschema,
|
||||
'conditions' => [
|
||||
{ 'bucket' => BUCKET },
|
||||
{ 'key' => random_filename},
|
||||
{ 'acl' => 'public-read' },
|
||||
{ 'Content-Type' => "image/jpeg"},
|
||||
['content-length-range', 1, 7.megabytes]
|
||||
]}.to_json).gsub(/\n/, '')
|
||||
end
|
||||
|
||||
def policy_signature
|
||||
@policy_signature ||= Base64.encode64(
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'),
|
||||
SECRET,
|
||||
policy)).gsub("\n",'')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
module Images
|
||||
class StubPolicy < Mutations::Command
|
||||
URL = "#{$API_URL}/direct_upload/"
|
||||
|
||||
def execute
|
||||
{
|
||||
verb: "POST",
|
||||
url: URL,
|
||||
form_data: {
|
||||
"key" => random_filename,
|
||||
"acl" => "public-read",
|
||||
"Content-Type" => "image/jpeg",
|
||||
"policy" => "N/A",
|
||||
"signature" => "N/A",
|
||||
"GoogleAccessId" => "N/A",
|
||||
"file" => "REPLACE_THIS_WITH_A_BINARY_JPEG_FILE"
|
||||
},
|
||||
instructions: "Send a 'from-data' request to the URL provided."\
|
||||
"Then POST the resulting URL as an 'attachment_url' "\
|
||||
"(json) to api/images/."
|
||||
}
|
||||
end
|
||||
private
|
||||
# The image URL in the "untrusted bucket" in Google Cloud Storage
|
||||
def random_filename
|
||||
@range ||= "temp/#{SecureRandom.uuid}.jpg"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,13 +20,13 @@ module Sequences
|
|||
|
||||
def execute
|
||||
ActiveRecord::Base.transaction do
|
||||
p = inputs
|
||||
.merge(migrated_nodes: true)
|
||||
.without(:body, :args, "body", "args")
|
||||
p = inputs
|
||||
.merge(migrated_nodes: true)
|
||||
.without(:body, :args, "body", "args")
|
||||
seq = Sequence.create!(p)
|
||||
x = CeleryScript::FirstPass.run!(sequence: seq,
|
||||
args: args || {},
|
||||
body: body || [])
|
||||
x = CeleryScript::FirstPass.run!(sequence: seq,
|
||||
args: args || {},
|
||||
body: body || [])
|
||||
result = CeleryScript::FetchCelery.run!(sequence: seq)
|
||||
seq.manually_sync! # We must manually sync this resource.
|
||||
result
|
||||
|
|
|
@ -5,8 +5,6 @@ class ImageSerializer < ActiveModel::Serializer
|
|||
def attachment_url
|
||||
url_ = object.attachment.url("x640")
|
||||
# Force google cloud users to use HTTPS://
|
||||
x = Api::ImagesController::KEY.present? ?
|
||||
url_.gsub("http://", "https://") : url_
|
||||
return x
|
||||
return ENV["GCS_KEY"].present? ? url_.gsub("http://", "https://") : url_
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,11 +68,12 @@ FarmBot::Application.routes.draw do
|
|||
# =======================================================================
|
||||
# NON-API (USER FACING) URLS:
|
||||
# =======================================================================
|
||||
get "/" => "dashboard#front_page", as: :front_page
|
||||
get "/app" => "dashboard#main_app", as: :dashboard
|
||||
get "/app/controls" => "dashboard#main_app", as: :app_landing_page
|
||||
get "/tos_update" => "dashboard#tos_update", as: :tos_update
|
||||
post "/csp_reports" => "dashboard#csp_reports", as: :csp_report
|
||||
get "/" => "dashboard#front_page", as: :front_page
|
||||
get "/app" => "dashboard#main_app", as: :dashboard
|
||||
get "/app/controls" => "dashboard#main_app", as: :app_landing_page
|
||||
get "/tos_update" => "dashboard#tos_update", as: :tos_update
|
||||
post "/csp_reports" => "dashboard#csp_reports", as: :csp_report
|
||||
post "/direct_upload" => "dashboard#direct_upload", as: :direct_upload
|
||||
|
||||
get "/password_reset/*token" => "dashboard#password_reset", as: :password_reset
|
||||
get "/verify/:token" => "dashboard#verify", as: :verify_user
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'spec_helper'
|
|||
describe Api::ImagesController do
|
||||
include Devise::Test::ControllerHelpers
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
it "Creates a polict object" do
|
||||
it "Creates a policy object" do
|
||||
sign_in user
|
||||
get :storage_auth
|
||||
|
||||
|
@ -16,6 +16,21 @@ describe Api::ImagesController do
|
|||
.to include("POST the resulting URL as an 'attachment_url'")
|
||||
end
|
||||
|
||||
it "Creates a (stub) policy object" do
|
||||
sign_in user
|
||||
b4 = Api::ImagesController.store_locally
|
||||
Api::ImagesController.store_locally = false
|
||||
get :storage_auth
|
||||
Api::ImagesController.store_locally = b4
|
||||
expect(response.status).to eq(200)
|
||||
expect(json).to be_kind_of(Hash)
|
||||
expect(json[:verb]).to eq("POST")
|
||||
expect(json[:url]).to include($API_URL)
|
||||
[ :policy, :signature, :GoogleAccessId ]
|
||||
.map { |key| expect(json.dig(:form_data, key)).to eq("N/A") }
|
||||
expect(json[:form_data].keys.sort).to include(:signature)
|
||||
end
|
||||
|
||||
describe '#index' do
|
||||
it 'shows only the max images allowed' do
|
||||
sign_in user
|
||||
|
|
|
@ -50,5 +50,16 @@ describe DashboardController do
|
|||
expect(user.reload.unconfirmed_email).to be nil
|
||||
expect(user.email).to eq email
|
||||
end
|
||||
|
||||
it 'handles self hosted image uploads' do
|
||||
name = "wow.jpg"
|
||||
params = {key: "whatever/" + name,
|
||||
file: StringIO.new(File.open("./spec/fixture.jpg").read)}
|
||||
post :direct_upload, params: params
|
||||
file = File.join("public", "direct_upload", "temp", name)
|
||||
expect(File.file?(file)).to be(true)
|
||||
expect(response.status).to be(200)
|
||||
File.delete(file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue