Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
source "https://rubygems.org"
ruby "~> 4.0.6"

gem "rails", "~> 8"
gem "rails"
gem "active_model_serializers"
gem "bunny"
gem "delayed_job_active_record"
gem "delayed_job", "4.1.13"
gem "devise"
gem "discard"
gem "google-cloud-storage", "~> 1.11"
gem "google-cloud-storage"
gem "jwt"
gem "kaminari"
gem "logger"
Expand Down
12 changes: 6 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ GEM
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.21.1)
json (2.21.2)
jsonapi-renderer (0.2.2)
jwt (3.2.0)
base64
Expand Down Expand Up @@ -354,7 +354,7 @@ GEM
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.4.2)
rbs (4.1.0)
rbs (4.1.1)
logger
prism (>= 1.6.0)
tsort
Expand Down Expand Up @@ -494,7 +494,7 @@ DEPENDENCIES
drb
factory_bot_rails
faker
google-cloud-storage (~> 1.11)
google-cloud-storage
hashdiff
irb
jwt
Expand All @@ -511,7 +511,7 @@ DEPENDENCIES
rabbitmq_http_api_client
rack-attack
rack-cors
rails (~> 8)
rails
redis (~> 4.0)
request_store
rollbar
Expand Down Expand Up @@ -599,7 +599,7 @@ CHECKSUMS
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
json (2.21.1) sha256=13a43df75d95641443f5702dff350f237164a9d811ff0f2c2800d4d980220583
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
jsonapi-renderer (0.2.2) sha256=b5c44b033d61b4abdb6500fa4ab84807ca0b36ea0e59e47a2c3ca7095a6e447b
jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7
kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e
Expand Down Expand Up @@ -667,7 +667,7 @@ CHECKSUMS
railties (8.1.3.1) sha256=2388a232579a00cefea4487de66c8553c3408c1300abdc6cf1799d86ffb04487
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
rbs (4.1.0) sha256=8baba59008b0643b4ba2090e9b1d0149655b0bbd42eb2ffe42b1d0eb6923fd72
rbs (4.1.1) sha256=1bf118f2f95d1cd3956357645d495152b661e0257e57781d18ceecd461622b9e
rbtree (0.4.7) sha256=1efabbcb3fd5f12249c9c8a610a765074868164eed0c50c9db2531c00ed161cb
rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
redis (4.8.1) sha256=387ee086694fffc9632aaeb1efe4a7b1627ca783bf373320346a8a20cd93333a
Expand Down
5 changes: 1 addition & 4 deletions app/controllers/api/featured_sequences_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
module Api
class FeaturedSequencesController < Api::AbstractController
JOIN = "INNER JOIN sequence_publications ON sequence_publications.id = sequence_versions.sequence_publication_id"

skip_before_action :authenticate_user!, only: [:index]

def index
Expand All @@ -17,9 +15,8 @@ def publisher_email
def publications
Rails.cache.fetch("farmbot_featured_sequences", expires_in: 10.minutes) do
SequenceVersion
.joins(JOIN)
.publicly_available
.where(sequence_publications: { cached_author_email: publisher_email })
.where(sequence_publications: { published: true })
.order(updated_at: :desc)
.uniq(&:sequence_publication_id)
.map do |x|
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/api/sequence_versions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def version_meta
end

def sequence_version
@sequence_version ||= SequenceVersion.find(params.expect(:id))
@sequence_version ||=
SequenceVersion.publicly_available.find(params.expect(:id))
end
end
end
7 changes: 4 additions & 3 deletions app/controllers/api/tools_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def show
end

def destroy
mutate Tools::Destroy.run(tool: tool)
mutate Tools::Destroy.run(tool: tool, device: current_device)
end

def create
Expand All @@ -23,7 +23,7 @@ def update
private

def update_params
output = raw_json.merge(tool: tool)
output = raw_json.merge(tool: tool, device: current_device)
output[:name] = params[:name] if params[:name]
output
end
Expand All @@ -33,7 +33,8 @@ def tools
end

def tool
@tool ||= Tool.join_tool_slot_and_find_by_id(params.expect(:id).to_i)
@tool ||= Tool.join_tool_slot_and_find_by_id(params.expect(:id).to_i,
current_device.id)
end
end
end
26 changes: 26 additions & 0 deletions app/controllers/dashboard_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,35 @@ def csp_reports
# (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
unless Image.valid_direct_upload_token?(key: params[:key],
token: params[:signature])
render json: { error: "Invalid upload signature" }, status: :unauthorized
return
end

Image.self_hosted_image_upload(key: params.fetch(:key),
file: params.fetch(:file))
render json: ""
rescue Image::DirectUploadTooLarge
render json: { error: "Image exceeds maximum size" },
status: :content_too_large
rescue Image::InvalidDirectUploadImage
render json: { error: "Upload must be a JPEG image" },
status: :unsupported_media_type
end

def direct_upload_file
filename = File.basename(params.expect(:filename))
path = Image.direct_upload_path("#{filename}.jpg")
unless File.file?(path)
head :not_found
return
end

response.headers["X-Content-Type-Options"] = "nosniff"
send_file path,
type: "image/jpeg",
disposition: "inline"
end

def logout; end
Expand Down
11 changes: 11 additions & 0 deletions app/jobs/password_reset_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class PasswordResetJob < ApplicationJob
queue_as :default

def perform(email)
user = User.find_by(email: email.strip.downcase)
return unless user

token = PasswordResetToken.issue_to(user).encoded
UserMailer.password_reset(user, token).deliver_later
end
end
35 changes: 34 additions & 1 deletion app/models/image.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
require "open-uri"
require "marcel"
# A set of image URLs (thumbs) + Associated meta data.
class Image < ApplicationRecord
class DirectUploadTooLarge < StandardError; end
class InvalidDirectUploadImage < StandardError; end

belongs_to :device
validates :device, presence: true
serialize :meta, coder: YAML
Expand All @@ -23,6 +27,7 @@ def set_defaults
x80: "80x80>",
}
MAX_IMAGE_SIZE = 7.megabytes
DIRECT_UPLOAD_TOKEN_TTL = 1.hour
CONFIG = { default_url: DEFAULT_URL,
styles: RMAGICK_STYLES,
size: { in: 0..MAX_IMAGE_SIZE } }
Expand Down Expand Up @@ -113,7 +118,35 @@ def self.self_hosted_image_upload(key:, file:)

name = key.split("/").last
src = file.tempfile.path
dest = File.join("public", "direct_upload", "temp", name)
raise DirectUploadTooLarge if File.size(src) > MAX_IMAGE_SIZE

detected_type = Marcel::MimeType.for(Pathname.new(src))
raise InvalidDirectUploadImage unless detected_type == "image/jpeg"

FileUtils.mkdir_p(direct_upload_directory)
dest = direct_upload_path(name)
FileUtils.mv(src, dest)
end

def self.direct_upload_path(name)
direct_upload_directory.join(File.basename(name))
end

def self.direct_upload_token(key)
direct_upload_verifier.generate(key, expires_in: DIRECT_UPLOAD_TOKEN_TTL)
end

def self.valid_direct_upload_token?(key:, token:)
token.present? && direct_upload_verifier.verified(token) == key
end

def self.direct_upload_verifier
Rails.application.message_verifier("direct_upload")
end
private_class_method :direct_upload_verifier

def self.direct_upload_directory
Rails.root.join("tmp/direct_upload")
end
private_class_method :direct_upload_directory
end
10 changes: 10 additions & 0 deletions app/models/sequence_version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ class SequenceVersion < ApplicationRecord
belongs_to :sequence_publication
has_one :fragment, as: :owner

scope :publicly_available, lambda {
joins(:sequence_publication)
.where(sequence_versions: { withdrawn_at: nil })
.where(sequence_publications: { published: true })
}

# We need a #device method on this resource
# because Fragment::Create expects it.
# it is OK to provide a `nil` device.
def device; nil end

def publicly_available?
withdrawn_at.nil? && sequence_publication.published?
end

def broadcast?
false
end
Expand Down
7 changes: 4 additions & 3 deletions app/models/tool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Tool < ApplicationRecord
"points" ON "points"."tool_id" = "tools"."id"
WHERE'
INDEX_QUERY = BASE + ' "tools"."device_id" = %s;'
SHOW_QUERY = BASE + ' "tools"."id" = %s;'
SHOW_QUERY = BASE + ' "tools"."id" = %s AND "tools"."device_id" = %s;'
IN_USE = "Tool in use by the following sequences: %s"

belongs_to :device
Expand All @@ -26,9 +26,10 @@ def self.outer_join_slots(device_id)
self.find_by_sql(INDEX_QUERY % device_id)
end

def self.join_tool_slot_and_find_by_id(id)
def self.join_tool_slot_and_find_by_id(id, device_id)
# Adding the || self.find part to raise 404 like "normal" AR queries.
# TODO: Clean this whole thing up - RC 2-may-18
self.find_by_sql(SHOW_QUERY % id).first || self.find(id)
self.find_by_sql(format(SHOW_QUERY, id, device_id)).first ||
self.where(device_id: device_id).find(id)
end
end
3 changes: 1 addition & 2 deletions app/mutations/devices/seeders/abstract_seeder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,8 @@ def tools_rotary; end

def install_sequence_version_by_name(name)
sv = SequenceVersion
.joins(Api::FeaturedSequencesController::JOIN)
.publicly_available
.where(sequence_publications: { cached_author_email: ENV["AUTHORIZED_PUBLISHER"] })
.where(sequence_publications: { published: true })
.order(updated_at: :desc)
.uniq(&:sequence_publication_id)
.filter { |x| x.name == name }[0]
Expand Down
4 changes: 3 additions & 1 deletion app/mutations/devices/seeders/demo_account_seeder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ def after_product_line_seeder(product_line)
end
add_point_groups
tool = device.tools.find_by(name: ToolNames::WATERING_NOZZLE)
Tools::Update.run(tool: tool, flow_rate_ml_per_s: 100) if tool
Tools::Update.run(tool: tool,
device: device,
flow_rate_ml_per_s: 100) if tool
add_envs

marketing_bulletin
Expand Down
13 changes: 11 additions & 2 deletions app/mutations/images/generate_policy.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "google/cloud/storage"
require "google/cloud/storage/file"
require "stringio"

module Images
class GeneratePolicy < Mutations::Command
Expand Down Expand Up @@ -33,8 +34,16 @@ def bucket_name
end

def bucket
json_key = ENV["GOOGLE_CLOUD_KEYFILE_JSON"]
json_key && Google::Cloud::Storage.new.bucket(bucket_name)
json_key = ENV["GCS_UPLOAD_KEYFILE_JSON"]
return unless json_key

credentials = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(json_key),
scope: Google::Cloud::Storage::Credentials::SCOPE,
)
Google::Cloud::Storage.new(
credentials: credentials,
).bucket(bucket_name, skip_lookup: true)
end

def post_object
Expand Down
5 changes: 3 additions & 2 deletions app/mutations/images/stub_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ class StubPolicy < Mutations::Command
URL = "#{$API_URL}/direct_upload/"

def execute
key = random_filename
{
verb: "POST",
url: URL,
form_data: {
"key" => random_filename,
"key" => key,
"acl" => "public-read",
"Content-Type" => "image/jpeg",
"policy" => "N/A",
"signature" => "N/A",
"signature" => Image.direct_upload_token(key),
"GoogleAccessId" => "N/A",
"file" => "REPLACE_THIS_WITH_A_BINARY_JPEG_FILE",
},
Expand Down
22 changes: 3 additions & 19 deletions app/mutations/password_resets/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,16 @@ class Create < Mutations::Command
string :email
end

def validate
email_not_found! unless user
end

def execute
send_email
PasswordResetJob.perform_later(normalized_email)
# Under no circumstance should you return the token.
return { status: "Check your email!" }
end

private

def send_email
UserMailer.password_reset(user, token).deliver_later
end

def token
@token ||= PasswordResetToken.issue_to(user).encoded
end

def email_not_found!
add_error :email, :not_found, "Email not found"
end

def user
@user ||= User.find_by(email: email)
def normalized_email
@normalized_email ||= email.strip.downcase
end
end
end
2 changes: 1 addition & 1 deletion app/mutations/sequences/install.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def execute
private

def validate_publication
unless sequence_version.sequence_publication.published
unless sequence_version.publicly_available?
add_error :sequence_version, :version, NOT_PUBLISHED
end
end
Expand Down
Loading
Loading