Merge branch 'master' of https://github.com/FarmBot/Farmbot-Web-App into finally_fix_devices_tsx
commit
10b0922acf
3
Gemfile
3
Gemfile
|
@ -24,12 +24,13 @@ gem "webpack-rails"
|
|||
# Active on the "official" FarmBot server, set the appropriate ENV
|
||||
# vars if you wish to use them on your own servers.
|
||||
gem "rollbar"
|
||||
gem "skylight"
|
||||
gem "skylight", "1.4.0"
|
||||
|
||||
group :development, :test do
|
||||
gem "codecov", require: false
|
||||
gem "database_cleaner"
|
||||
gem "pry"
|
||||
gem "pry-rails"
|
||||
gem "factory_girl_rails"
|
||||
gem "faker"
|
||||
gem "smarf_doc", git: "https://github.com/RickCarlino/smarf_doc.git"
|
||||
|
|
|
@ -156,6 +156,8 @@ GEM
|
|||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
pry-rails (0.3.6)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.0.0)
|
||||
rack (2.0.3)
|
||||
rack-attack (5.0.1)
|
||||
|
@ -229,7 +231,7 @@ GEM
|
|||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
skylight (1.3.1)
|
||||
skylight (1.4.0)
|
||||
activesupport (>= 3.0.0)
|
||||
slop (3.6.0)
|
||||
sprockets (3.7.1)
|
||||
|
@ -278,6 +280,7 @@ DEPENDENCIES
|
|||
pg
|
||||
polymorphic_constraints
|
||||
pry
|
||||
pry-rails
|
||||
rack-attack
|
||||
rack-cors
|
||||
rails
|
||||
|
@ -287,7 +290,7 @@ DEPENDENCIES
|
|||
rspec (~> 3.5.0)
|
||||
rspec-rails (~> 3.5.0)
|
||||
simplecov
|
||||
skylight
|
||||
skylight (= 1.4.0)
|
||||
smarf_doc!
|
||||
thin
|
||||
tzinfo
|
||||
|
|
|
@ -8,30 +8,10 @@ module Api
|
|||
# If some logs fail to save, they will fail silently.
|
||||
# As a matter of policy, not all log types are stored in the DB.
|
||||
def create
|
||||
case raw_json
|
||||
when Array
|
||||
logs = Log
|
||||
.create(raw_json.last(current_device.max_log_count)
|
||||
.map { |i| new_log(i) }
|
||||
.select { |i| i.success? } # <= Ignore rejects
|
||||
.map { |i| i.result }
|
||||
.reject do |i|
|
||||
# Don't save jokes or debug info:
|
||||
t = i.meta["type"]
|
||||
["fun", "debug"].include?(t)
|
||||
end
|
||||
.map { |i| i.as_json })
|
||||
.tap { |i| maybe_deliver(i) }
|
||||
render json: logs
|
||||
when Hash
|
||||
outcome = new_log(raw_json)
|
||||
if outcome.success?
|
||||
outcome.result.save!
|
||||
maybe_deliver(outcome.result)
|
||||
end
|
||||
return mutate outcome
|
||||
else
|
||||
sorry "Post a JSON array or object.", 422
|
||||
case raw_json
|
||||
when Array then handle_many_logs
|
||||
when Hash then handle_single_log
|
||||
else; sorry "Post a JSON array or object.", 422
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -47,12 +27,25 @@ module Api
|
|||
|
||||
private
|
||||
|
||||
def handle_many_logs
|
||||
mutate Logs::BatchCreate.run(device: current_device, logs: raw_json)
|
||||
end
|
||||
|
||||
def handle_single_log
|
||||
outcome = new_log(raw_json)
|
||||
if outcome.success?
|
||||
outcome.result.save!
|
||||
maybe_deliver(outcome.result)
|
||||
end
|
||||
mutate outcome
|
||||
end
|
||||
|
||||
def new_log(input)
|
||||
Logs::Create.run(input, device: current_device)
|
||||
end
|
||||
|
||||
def maybe_deliver(log_or_logs)
|
||||
LogDispatch.deliver(current_device, log_or_logs)
|
||||
LogDispatch.delay.deliver(current_device, log_or_logs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,21 +24,26 @@ module Api
|
|||
end
|
||||
|
||||
private
|
||||
|
||||
# Don't proceed with login if they need to sign the EULA
|
||||
instrument_method
|
||||
def maybe_halt_login(result)
|
||||
result.result[:user].try(:require_consent!) if result.success?
|
||||
end
|
||||
|
||||
instrument_method
|
||||
def guess_aud_claim
|
||||
when_farmbot_os { return AbstractJwtToken::BOT_AUD }
|
||||
return AbstractJwtToken::HUMAN_AUD if xhr?
|
||||
AbstractJwtToken::UNKNOWN_AUD
|
||||
end
|
||||
|
||||
instrument_method
|
||||
def xhr? # I only wrote this because `request.xhr?` refused to be stubbed
|
||||
request.xhr?
|
||||
end
|
||||
|
||||
instrument_method
|
||||
def if_properly_formatted
|
||||
user = params.as_json.deep_symbolize_keys.fetch(:user, {})
|
||||
# If data handling for this method gets any more complicated,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
class BatchLogDispatchJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(device, logs)
|
||||
LogDispatch.deliver(device, Log.create!(logs))
|
||||
end
|
||||
end
|
|
@ -15,9 +15,9 @@ class Device < ApplicationRecord
|
|||
has_many :tools, dependent: :destroy
|
||||
has_many :images, dependent: :destroy
|
||||
has_many :webcam_feeds, dependent: :destroy
|
||||
validates :timezone, inclusion: { in: TIMEZONES,
|
||||
message: BAD_TZ,
|
||||
allow_nil: true }
|
||||
validates :timezone, inclusion: { in: TIMEZONES,
|
||||
message: BAD_TZ,
|
||||
allow_nil: true }
|
||||
validates_presence_of :name
|
||||
|
||||
# Give the user back the amount of logs they are allowed to view.
|
||||
|
|
|
@ -15,8 +15,8 @@ class FarmEvent < ApplicationRecord
|
|||
validate :within_20_year_window
|
||||
|
||||
def within_20_year_window
|
||||
too_early = start_time < (Time.now - 20.years)
|
||||
too_late = end_time > (Time.now + 20.years)
|
||||
too_early = start_time && start_time < (Time.now - 20.years)
|
||||
too_late = end_time && end_time > (Time.now + 20.years)
|
||||
errors.add :start_time, "too far in the past" if too_early
|
||||
errors.add :end_time, "too far in the future" if too_late
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module Auth
|
||||
class CreateToken < Mutations::Command
|
||||
include Auth::ConsentHelpers
|
||||
include Skylight::Helpers
|
||||
|
||||
required do
|
||||
string :email
|
||||
|
@ -14,6 +15,7 @@ module Auth
|
|||
default: AbstractJwtToken::UNKNOWN_AUD
|
||||
end
|
||||
|
||||
instrument_method
|
||||
def validate
|
||||
@user = User.where(email: email.downcase).first
|
||||
whoops! unless @user && @user.valid_password?(password)
|
||||
|
@ -22,6 +24,7 @@ module Auth
|
|||
end
|
||||
end
|
||||
|
||||
instrument_method
|
||||
def execute
|
||||
@user.update_attributes(agreed_to_terms_at: Time.now) if agree_to_terms
|
||||
SessionToken.as_json(@user, aud)
|
||||
|
@ -29,6 +32,7 @@ module Auth
|
|||
|
||||
private
|
||||
|
||||
instrument_method
|
||||
def whoops!
|
||||
add_error :auth, :*, "Bad email or password."
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
module Auth
|
||||
class CreateTokenFromCredentials < Mutations::Command
|
||||
include Skylight::Helpers
|
||||
|
||||
PRIVATE_KEY = KeyGen.current
|
||||
BAD_KEY = "You are most likely on the wrong server env. That's not a "\
|
||||
"valid credentials file."
|
||||
|
@ -10,6 +12,7 @@ module Auth
|
|||
string :credentials
|
||||
end
|
||||
|
||||
instrument_method
|
||||
def validate
|
||||
cipher_text = Base64.decode64(credentials)
|
||||
plain_text = PRIVATE_KEY.private_decrypt(cipher_text)
|
||||
|
@ -20,12 +23,14 @@ module Auth
|
|||
whoops!(BAD_KEY)
|
||||
end
|
||||
|
||||
instrument_method
|
||||
def execute
|
||||
SessionToken.as_json(user, AbstractJwtToken::BOT_AUD)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
instrument_method
|
||||
def whoops!(reason = "Bad email or password.")
|
||||
add_error :auth, :*, reason
|
||||
end
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
module Logs
|
||||
class BatchCreate < Mutations::Command
|
||||
required do
|
||||
model :device, class: Device
|
||||
array :logs do
|
||||
hash do
|
||||
string :message
|
||||
optional do
|
||||
array :channels,
|
||||
class: String,
|
||||
in: CeleryScriptSettingsBag::ALLOWED_CHANNEL_NAMES
|
||||
hash :meta do
|
||||
integer :x
|
||||
integer :y
|
||||
integer :z
|
||||
string :type, in: Log::TYPES
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def execute
|
||||
BatchLogDispatchJob.perform_later(device, clean_logs)
|
||||
return clean_logs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clean_logs
|
||||
@clean_logs ||= logs
|
||||
.last(device.max_log_count)
|
||||
.map { |i| Logs::Create.run(i, device: device) }
|
||||
.select { |i| i.success? } # <= Ignore rejects
|
||||
.map { |i| i.result }
|
||||
.reject do |i|
|
||||
# Don't save jokes or debug info:
|
||||
t = i.meta["type"]
|
||||
["fun", "debug"].include?(t)
|
||||
end
|
||||
.map { |i| i.as_json.except("id") }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -9,7 +9,9 @@ module Logs
|
|||
end
|
||||
|
||||
optional do
|
||||
array :channels, class: String
|
||||
array :channels,
|
||||
class: String,
|
||||
in: CeleryScriptSettingsBag::ALLOWED_CHANNEL_NAMES
|
||||
hash :meta do
|
||||
integer :x
|
||||
integer :y
|
||||
|
|
|
@ -48,19 +48,21 @@ describe Api::LogsController do
|
|||
it 'creates many logs (with an Array)' do
|
||||
sign_in user
|
||||
before_count = Log.count
|
||||
post :create,
|
||||
body: [
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["toast"],
|
||||
message: "one" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["toast"],
|
||||
message: "two" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["toast"],
|
||||
message: "three" },
|
||||
].to_json,
|
||||
params: {format: :json}
|
||||
run_jobs_now do
|
||||
post :create,
|
||||
body: [
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["toast"],
|
||||
message: "one" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["toast"],
|
||||
message: "two" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["toast"],
|
||||
message: "three" },
|
||||
].to_json,
|
||||
params: {format: :json}
|
||||
end
|
||||
expect(response.status).to eq(200)
|
||||
expect(before_count + 3).to eq(Log.count)
|
||||
end
|
||||
|
@ -68,39 +70,51 @@ describe Api::LogsController do
|
|||
it 'does not bother saving `fun` or `debug` logs' do
|
||||
sign_in user
|
||||
Log.destroy_all
|
||||
LogDispatch.destroy_all
|
||||
before_count = Log.count
|
||||
dispatch_before = LogDispatch.count
|
||||
post :create,
|
||||
body: [
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["toast"],
|
||||
message: "one" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "fun" }, # Ignored
|
||||
channels: [],
|
||||
message: "two" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "debug" }, # Ignored
|
||||
channels: [],
|
||||
message: "two" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["email"],
|
||||
message: "three" },
|
||||
].to_json,
|
||||
params: {format: :json}
|
||||
expect(response.status).to eq(200)
|
||||
expect(before_count + 2).to eq(Log.count)
|
||||
expect(dispatch_before + 1).to eq(LogDispatch.count)
|
||||
run_jobs_now do
|
||||
post :create,
|
||||
body: [
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["toast"],
|
||||
message: "one" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "fun" }, # Ignored
|
||||
channels: [],
|
||||
message: "two" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "debug" }, # Ignored
|
||||
channels: [],
|
||||
message: "two" },
|
||||
{ meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["email"],
|
||||
message: "three" },
|
||||
].to_json,
|
||||
params: {format: :json}
|
||||
expect(response.status).to eq(200)
|
||||
expect(Log.count).to eq(before_count + 2)
|
||||
expect(LogDispatch.count).to eq(dispatch_before + 1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'Runs compaction when the logs pile up' do
|
||||
stub = {
|
||||
meta: { x: 1, y: 2, z: 3, type: "info" }, channels: ["toast"],
|
||||
message: "one" }
|
||||
payl = []
|
||||
100.times { payl.push(stub) }
|
||||
100.times do
|
||||
payl.push({ meta: { x: 1,
|
||||
y: 2,
|
||||
z: 3,
|
||||
type: "info"
|
||||
},
|
||||
channels: ["toast"],
|
||||
message: "one" })
|
||||
end
|
||||
sign_in user
|
||||
user.device.update_attributes!(max_log_count: 15)
|
||||
LogDispatch.destroy_all
|
||||
Log.destroy_all
|
||||
before_count = Log.count
|
||||
post :create, body: payl.to_json, params: {format: :json}
|
||||
run_jobs_now do
|
||||
post :create, body: payl.to_json, params: {format: :json}
|
||||
end
|
||||
expect(response.status).to eq(200)
|
||||
expect(json.length).to eq(user.device.max_log_count)
|
||||
end
|
||||
|
@ -121,14 +135,16 @@ describe Api::LogsController do
|
|||
body = { meta: { x: 1, y: 2, z: 3, type: "info" },
|
||||
channels: ["email"],
|
||||
message: "Heyoooo" }.to_json
|
||||
post :create, body: body, params: {format: :json}
|
||||
after_count = LogDispatch.count
|
||||
expect(response.status).to eq(200)
|
||||
expect(last_email).to be
|
||||
expect(last_email.body.to_s).to include("Heyoooo")
|
||||
expect(last_email.to).to include(user.email)
|
||||
expect(before_count).to be < after_count
|
||||
expect(LogDispatch.where(sent_at: nil).count).to eq(0)
|
||||
run_jobs_now do
|
||||
post :create, body: body, params: {format: :json}
|
||||
after_count = LogDispatch.count
|
||||
expect(response.status).to eq(200)
|
||||
expect(last_email).to be
|
||||
expect(last_email.body.to_s).to include("Heyoooo")
|
||||
expect(last_email.to).to include(user.email)
|
||||
expect(before_count).to be < after_count
|
||||
expect(LogDispatch.where(sent_at: nil).count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
it "handles bug that Connor reported" do
|
||||
|
|
|
@ -3,10 +3,28 @@ require 'spec_helper'
|
|||
describe Auth::FromJWT do
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
|
||||
it 'gets user from jwt' do
|
||||
|
||||
def fake_credentials(email, password)
|
||||
# Input -> JSONify -> encrypt -> Base64ify
|
||||
secret = { email: email, password: password }.to_json
|
||||
ct = KeyGen.current.public_encrypt(secret)
|
||||
return Base64.encode64(ct)
|
||||
end
|
||||
|
||||
it 'rejects bad credentials' do
|
||||
results = Auth::CreateTokenFromCredentials.run(credentials: "FOO" )
|
||||
expect(results.success?).to eq(false)
|
||||
expect(results.errors.message_list)
|
||||
.to include(Auth::CreateTokenFromCredentials::BAD_KEY)
|
||||
end
|
||||
|
||||
it 'accepts good credentials' do
|
||||
pw = "password123"
|
||||
user = FactoryGirl.create(:user, password: pw)
|
||||
email = user.email
|
||||
creds = fake_credentials(email, pw)
|
||||
results = Auth::CreateTokenFromCredentials.run!(credentials: creds)
|
||||
expect(results[:token]).to be_kind_of(SessionToken)
|
||||
expect(results[:user]).to eq(user)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue