Add support for Overland

This commit is contained in:
Eugene Burmakin 2024-04-06 19:09:38 +02:00
parent f6ae4aef8c
commit fe4110b0fd
14 changed files with 195 additions and 7 deletions

View file

@ -0,0 +1,15 @@
class Api::V1::Overland::BatchesController < ApplicationController
skip_forgery_protection
def create
Overland::BatchCreatingJob.perform_later(batch_params)
render json: { result: 'ok' }, status: :created
end
private
def batch_params
params.permit(locations: [:type, geometry: {}, properties: {}], batch: {})
end
end

View file

@ -2,7 +2,7 @@ class Api::V1::PointsController < ApplicationController
skip_forgery_protection
def create
PointCreatingJob.perform_later(point_params)
Owntracks::PointCreatingJob.perform_later(point_params)
render json: {}, status: :ok
end

View file

@ -0,0 +1,11 @@
class Overland::BatchCreatingJob < ApplicationJob
queue_as :default
def perform(params)
data = Overland::Params.new(params).call
data.each do |location|
Point.create!(location)
end
end
end

View file

@ -1,4 +1,4 @@
class PointCreatingJob < ApplicationJob
class Owntracks::PointCreatingJob < ApplicationJob
queue_as :default
def perform(point_params)

View file

@ -1,7 +1,7 @@
class Point < ApplicationRecord
belongs_to :import, optional: true
validates :latitude, :longitude, :tracker_id, :timestamp, :topic, presence: true
validates :latitude, :longitude, :timestamp, presence: true
enum battery_status: { unknown: 0, unplugged: 1, charging: 2, full: 3 }, _suffix: true
enum trigger: {

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
class Overland::Params
attr_reader :data, :points
def initialize(json)
@data = json.with_indifferent_access
@points = @data[:locations]
end
def call
points.map do |point|
{
latitude: point[:geometry][:coordinates][1],
longitude: point[:geometry][:coordinates][0],
battery_status: point[:properties][:battery_state],
battery: battery_level(point[:properties][:battery_level]),
timestamp: DateTime.parse(point[:properties][:timestamp]),
altitude: point[:properties][:altitude],
velocity: point[:properties][:speed],
tracker_id: point[:properties][:device_id],
ssid: point[:properties][:wifi],
accuracy: point[:properties][:horizontal_accuracy],
vertical_accuracy: point[:properties][:vertical_accuracy],
raw_data: point
}
end
end
private
def battery_level(level)
value = (level.to_f * 100).to_i
value.positive? ? value : nil
end
end

View file

@ -20,6 +20,10 @@ Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :points
namespace :overland do
resources :batches, only: :create
end
end
end
end

31
spec/fixtures/overland/geodata.json vendored Normal file
View file

@ -0,0 +1,31 @@
{
"locations": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-122.030581, 37.331800
]
},
"properties": {
"timestamp": "2015-10-01T08:00:00-0700",
"altitude": 0,
"speed": 4,
"horizontal_accuracy": 30,
"vertical_accuracy": -1,
"motion": ["driving","stationary"],
"pauses": false,
"activity": "other_navigation",
"desired_accuracy": 100,
"deferred": 1000,
"significant_change": "disabled",
"locations_in_payload": 1,
"device_id": "",
"wifi": "launchpad",
"battery_state": "charging",
"battery_level": 0.89
}
}
]
}

View file

@ -0,0 +1,15 @@
require 'rails_helper'
RSpec.describe Overland::BatchCreatingJob, type: :job do
describe '#perform' do
subject(:perform) { described_class.new.perform(json) }
let(:file_path) { 'spec/fixtures/overland/geodata.json' }
let(:file) { File.open(file_path) }
let(:json) { JSON.parse(file.read) }
it 'creates a location' do
expect { perform }.to change { Point.count }.by(1)
end
end
end

View file

@ -1,6 +1,6 @@
require 'rails_helper'
RSpec.describe PointCreatingJob, type: :job do
RSpec.describe Owntracks::PointCreatingJob, type: :job do
describe '#perform' do
subject(:perform) { described_class.new.perform(point_params) }

View file

@ -8,8 +8,9 @@ RSpec.describe Point, type: :model do
describe 'validations' do
it { is_expected.to validate_presence_of(:latitude) }
it { is_expected.to validate_presence_of(:longitude) }
it { is_expected.to validate_presence_of(:tracker_id) }
it { is_expected.to validate_presence_of(:timestamp) }
it { is_expected.to validate_presence_of(:topic) }
# Disabled them (for now) because they are not present in the Overland data
xit { is_expected.to validate_presence_of(:tracker_id) }
xit { is_expected.to validate_presence_of(:topic) }
end
end

View file

@ -0,0 +1,22 @@
require 'rails_helper'
RSpec.describe "Api::V1::Overland::Batches", type: :request do
describe "POST /index" do
let(:file_path) { 'spec/fixtures/overland/geodata.json' }
let(:file) { File.open(file_path) }
let(:json) { JSON.parse(file.read) }
let(:params) { json }
it 'returns http success' do
post '/api/v1/overland/batches', params: params
expect(response).to have_http_status(:created)
end
it 'enqueues a job' do
expect do
post '/api/v1/overland/batches', params: params
end.to have_enqueued_job(Overland::BatchCreatingJob)
end
end
end

View file

@ -16,7 +16,7 @@ RSpec.describe "Api::V1::Points", type: :request do
it 'enqueues a job' do
expect {
post api_v1_points_path, params: params
}.to have_enqueued_job(PointCreatingJob)
}.to have_enqueued_job(Owntracks::PointCreatingJob)
end
end
end

View file

@ -0,0 +1,52 @@
require 'rails_helper'
RSpec.describe Overland::Params do
describe '#call' do
let(:file_path) { 'spec/fixtures/overland/geodata.json' }
let(:file) { File.open(file_path) }
let(:json) { JSON.parse(file.read) }
let(:expected_json) do
{
latitude: 37.3318,
longitude: -122.030581,
battery_status: 'charging',
battery: 89,
altitude: 0,
accuracy: 30,
vertical_accuracy: -1,
velocity: 4,
ssid: 'launchpad',
tracker_id: '',
timestamp: DateTime.parse('2015-10-01T08:00:00-0700'),
raw_data: json['locations'][0]
}
end
subject(:params) { described_class.new(json).call }
it 'returns a hash with the correct keys' do
expect(params[0].keys).to match_array(
%i[
latitude
longitude
battery_status
battery
altitude
accuracy
vertical_accuracy
velocity
ssid
tracker_id
timestamp
raw_data
]
)
end
it 'returns a hash with the correct values' do
expect(params[0]).to eq(expected_json)
end
end
end