diff --git a/app/controllers/api/v1/overland/batches_controller.rb b/app/controllers/api/v1/overland/batches_controller.rb new file mode 100644 index 00000000..c2ce454f --- /dev/null +++ b/app/controllers/api/v1/overland/batches_controller.rb @@ -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 diff --git a/app/controllers/api/v1/points_controller.rb b/app/controllers/api/v1/points_controller.rb index 98aeea70..d991a49a 100644 --- a/app/controllers/api/v1/points_controller.rb +++ b/app/controllers/api/v1/points_controller.rb @@ -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 diff --git a/app/jobs/overland/batch_creating_job.rb b/app/jobs/overland/batch_creating_job.rb new file mode 100644 index 00000000..90a07ba3 --- /dev/null +++ b/app/jobs/overland/batch_creating_job.rb @@ -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 diff --git a/app/jobs/point_creating_job.rb b/app/jobs/owntracks/point_creating_job.rb similarity index 75% rename from app/jobs/point_creating_job.rb rename to app/jobs/owntracks/point_creating_job.rb index c7899d04..a1324ccf 100644 --- a/app/jobs/point_creating_job.rb +++ b/app/jobs/owntracks/point_creating_job.rb @@ -1,4 +1,4 @@ -class PointCreatingJob < ApplicationJob +class Owntracks::PointCreatingJob < ApplicationJob queue_as :default def perform(point_params) diff --git a/app/models/point.rb b/app/models/point.rb index 1876fd54..a7a080a7 100644 --- a/app/models/point.rb +++ b/app/models/point.rb @@ -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: { diff --git a/app/services/overland/params.rb b/app/services/overland/params.rb new file mode 100644 index 00000000..f003278b --- /dev/null +++ b/app/services/overland/params.rb @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 380db1cf..c1e016f4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/spec/fixtures/overland/geodata.json b/spec/fixtures/overland/geodata.json new file mode 100644 index 00000000..4d81f298 --- /dev/null +++ b/spec/fixtures/overland/geodata.json @@ -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 + } + } + ] +} diff --git a/spec/jobs/overland/batch_creating_job_spec.rb b/spec/jobs/overland/batch_creating_job_spec.rb new file mode 100644 index 00000000..cf19bb7e --- /dev/null +++ b/spec/jobs/overland/batch_creating_job_spec.rb @@ -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 diff --git a/spec/jobs/point_creating_job_spec.rb b/spec/jobs/owntracks/point_creating_job_spec.rb similarity index 85% rename from spec/jobs/point_creating_job_spec.rb rename to spec/jobs/owntracks/point_creating_job_spec.rb index c5de9997..625fbce6 100644 --- a/spec/jobs/point_creating_job_spec.rb +++ b/spec/jobs/owntracks/point_creating_job_spec.rb @@ -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) } diff --git a/spec/models/point_spec.rb b/spec/models/point_spec.rb index 86284d69..5ff7afdb 100644 --- a/spec/models/point_spec.rb +++ b/spec/models/point_spec.rb @@ -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 diff --git a/spec/requests/api/v1/overland/batches_spec.rb b/spec/requests/api/v1/overland/batches_spec.rb new file mode 100644 index 00000000..3e0e93e9 --- /dev/null +++ b/spec/requests/api/v1/overland/batches_spec.rb @@ -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 diff --git a/spec/requests/api/v1/points_spec.rb b/spec/requests/api/v1/points_spec.rb index f7b817d4..91a24dea 100644 --- a/spec/requests/api/v1/points_spec.rb +++ b/spec/requests/api/v1/points_spec.rb @@ -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 diff --git a/spec/services/overland/params_spec.rb b/spec/services/overland/params_spec.rb new file mode 100644 index 00000000..6c8fcea1 --- /dev/null +++ b/spec/services/overland/params_spec.rb @@ -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 +