Fix owntracks import

This commit is contained in:
Eugene Burmakin 2024-03-23 13:36:09 +01:00
parent 2556e2ae70
commit 18ed732c24
12 changed files with 546 additions and 10 deletions

View file

@ -27,6 +27,8 @@ group :development, :test do
gem 'ffaker'
gem 'rspec-rails'
gem 'dotenv-rails'
gem 'pry-byebug'
gem 'pry-rails'
end
group :test do

View file

@ -82,6 +82,8 @@ GEM
bootsnap (1.18.3)
msgpack (~> 1.2)
builder (3.2.4)
byebug (11.1.3)
coderay (1.1.3)
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
crass (1.0.6)
@ -135,6 +137,7 @@ GEM
net-smtp
mapkick-rb (0.1.5)
marcel (1.0.4)
method_source (1.0.0)
mini_mime (1.1.5)
minitest (5.22.3)
msgpack (1.7.2)
@ -161,6 +164,14 @@ GEM
ast (~> 2.4.1)
racc
pg (1.5.6)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (>= 0.13, < 0.15)
pry-rails (0.3.9)
pry (>= 0.10.4)
psych (5.1.2)
stringio
puma (6.4.2)
@ -322,6 +333,8 @@ DEPENDENCIES
importmap-rails
mapkick-rb
pg
pry-byebug
pry-rails
puma
pundit
rails

View file

@ -13,6 +13,13 @@ class Api::V1::PointsController < ApplicationController
end
end
def destroy
@point = Point.find(params[:id])
@point.destroy
head :no_content
end
private
def point_params

View file

@ -20,7 +20,7 @@ class ImportsController < ApplicationController
files.each do |file|
json = JSON.parse(file.read)
import = current_user.imports.create(name: file.original_filename)
import = current_user.imports.create(name: file.original_filename, source: params[:import][:source])
parser.new(file.path, import.id).call
imports << import

View file

@ -7,8 +7,8 @@ class PointsController < ApplicationController
@countries_and_cities = CountriesAndCities.new(@points).call
@coordinates =
@points
.pluck(:latitude, :longitude, :battery, :altitude, :timestamp, :velocity)
.map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s] }
.pluck(:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id)
.map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s, _7] }
@distance = distance
@start_at = Time.at(start_at)
@end_at = Time.at(end_at)

View file

@ -30,6 +30,7 @@ export default class extends Controller {
<b>Altitude:</b> ${marker[3]}m<br>
<b>Velocity:</b> ${marker[5]}km/h<br>
<b>Battery:</b> ${marker[2]}%<br>
id: ${marker[6]}<br>
`;
}
@ -47,8 +48,6 @@ export default class extends Controller {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
setMap
addTileLayer(map) {
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,

View file

@ -4,7 +4,7 @@ class OwnTracks::Params
attr_reader :params
def initialize(params)
@params = params
@params = params.to_h.deep_symbolize_keys
end
def call
@ -23,11 +23,11 @@ class OwnTracks::Params
bssid: params[:BSSID],
trigger: trigger,
tracker_id: params[:tid],
timestamp: Time.at(params[:tst].to_i),
timestamp: params[:tst].to_i,
inrids: params[:inrids],
in_regions: params[:inregions],
topic: params[:topic],
raw_data: params
raw_data: params.deep_stringify_keys
}
end

View file

@ -5,6 +5,28 @@
</div>
<div id="imports" class="min-w-full">
<%= render @imports %>
<div class="overflow-x-auto">
<table class="table">
<!-- head -->
<thead>
<tr>
<th>Name</th>
<th>Points</th>
<th>Created at</th>
</tr>
</thead>
<tbody>
<% @imports.each do |import| %>
<tr>
<td>
<%= link_to import.name, import, class: 'underline hover:no-underline' %> (<%= import.source %>)
</td>
<td><%= import.points.count %></td>
<td><%= import.created_at.strftime("%d.%m.%Y, %H:%M") %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>

View file

@ -8,7 +8,7 @@
<%= link_to "Edit this import", edit_import_path(@import), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
<div class="inline-block ml-2">
<%= button_to "Destroy this import", import_path(@import), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
<%= link_to "Destroy this import", import_path(@import), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?", turbo_method: :delete }, method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
</div>
<%= link_to "Back to imports", imports_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
</div>

275
spec/fixtures/owntracks_export.json vendored Normal file
View file

@ -0,0 +1,275 @@
{
"test": {
"iphone-12-pro": [
{
"batt": 85,
"lon": -74.0060,
"acc": 8,
"bs": 2,
"inrids": [
"5f1d1b"
],
"BSSID": "b0:f2:8:45:94:33",
"SSID": "Home Wifi",
"vac": 3,
"inregions": [
"home"
],
"lat": 40.7128,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "p",
"conn": "w",
"m": 1,
"tst": 1706965203,
"alt": 41,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T13:00:03Z",
"isotst": "2024-02-03T13:00:03Z",
"disptst": "2024-02-03 13:00:03"
},
{
"batt": 89,
"lon": -0.1278,
"acc": 8,
"bs": 1,
"inrids": [
"5f1d1b"
],
"p": 101.233,
"BSSID": "b0:f2:8:45:94:33",
"SSID": "Home Wifi",
"vac": 15,
"inregions": [
"home"
],
"lat": 51.5074,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "p",
"conn": "w",
"m": 1,
"tst": 1706967037,
"alt": 36,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T13:30:37Z",
"isotst": "2024-02-03T13:30:37Z",
"disptst": "2024-02-03 13:30:37"
},
{
"cog": 11,
"batt": 94,
"lon": 139.6917,
"acc": 5,
"bs": 1,
"p": 101.318,
"vel": 3,
"vac": 3,
"lat": 35.6895,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "v",
"conn": "m",
"m": 1,
"tst": 1706971835,
"alt": 35,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d77d",
"isorcv": "2024-02-03T14:50:35Z",
"isotst": "2024-02-03T14:50:35Z",
"disptst": "2024-02-03 14:50:35"
},
{
"cog": 11,
"batt": 94,
"lon": 151.2093,
"acc": 5,
"bs": 1,
"p": 101.318,
"vel": 3,
"vac": 3,
"lat": -33.8688,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "c",
"conn": "m",
"m": 1,
"tst": 1706971835,
"alt": 35,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d77d",
"isorcv": "2024-02-03T14:50:35Z",
"isotst": "2024-02-03T14:50:35Z",
"disptst": "2024-02-03 14:50:35"
},
{
"cog": 47,
"batt": 94,
"lon": 2.3522,
"acc": 4,
"bs": 1,
"inrids": [
"5f1d1b"
],
"p": 101.326,
"vel": 4,
"vac": 4,
"inregions": [
"home"
],
"lat": 48.8566,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "v",
"conn": "m",
"m": 1,
"tst": 1706974182,
"alt": 42,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T15:29:42Z",
"isotst": "2024-02-03T15:29:42Z",
"disptst": "2024-02-03 15:29:42"
},
{
"batt": 94,
"lon": -43.1729,
"acc": 8,
"bs": 1,
"inrids": [
"5f1d1b"
],
"p": 101.253,
"BSSID": "b0:f2:8:45:94:33",
"SSID": "Home Wifi",
"vac": 11,
"inregions": [
"home"
],
"lat": -22.9068,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "p",
"conn": "w",
"m": 1,
"tst": 1706974302,
"alt": 37,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T15:31:42Z",
"isotst": "2024-02-03T15:31:42Z",
"disptst": "2024-02-03 15:31:42"
},
{
"batt": 94,
"lon": -43.1729,
"acc": 8,
"bs": 1,
"inrids": [
"5f1d1b"
],
"p": 101.256,
"BSSID": "b0:f2:8:45:94:33",
"SSID": "Home Wifi",
"vac": 17,
"inregions": [
"home"
],
"lat": -22.9068,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "p",
"conn": "w",
"m": 1,
"tst": 1706977057,
"alt": 36,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d773",
"isorcv": "2024-02-03T16:17:37Z",
"isotst": "2024-02-03T16:17:37Z",
"disptst": "2024-02-03 16:17:37"
},
{
"cog": 43,
"batt": 89,
"lon": 37.6176,
"acc": 5,
"bs": 1,
"p": 101.349,
"vel": 5,
"vac": 3,
"lat": 55.7558,
"topic": "owntracks/test/iPhone 12 Pro",
"t": "v",
"conn": "m",
"m": 1,
"tst": 1706981399,
"alt": 38,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d7ku",
"isorcv": "2024-02-03T17:29:59Z",
"isotst": "2024-02-03T17:29:59Z",
"disptst": "2024-02-03 17:29:59"
},
{
"cog": 53,
"batt": 89,
"lon": 37.6176,
"acc": 5,
"bs": 1,
"p": 101.353,
"vel": 5,
"vac": 3,
"lat": 55.7558,
"topic": "owntracks/test/iPhone 12 Pro",
"conn": "m",
"m": 1,
"tst": 1706981451,
"alt": 37,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d7kv",
"isorcv": "2024-02-03T17:30:51Z",
"isotst": "2024-02-03T17:30:51Z",
"disptst": "2024-02-03 17:30:51"
},
{
"cog": 53,
"batt": 89,
"lon": 37.6176,
"acc": 5,
"bs": 1,
"p": 101.359,
"created_at": 1706981777,
"vel": 5,
"vac": 3,
"lat": 55.7558,
"topic": "owntracks/test/iPhone 12 Pro",
"conn": "m",
"m": 1,
"tst": 1706981451,
"alt": 37,
"_type": "location",
"tid": "RO",
"_http": true,
"ghash": "u33d7kv",
"isorcv": "2024-02-03T17:30:51Z",
"isotst": "2024-02-03T17:30:51Z",
"disptst": "2024-02-03 17:30:51"
}
]
}
}

View file

@ -0,0 +1,24 @@
require 'rails_helper'
RSpec.describe OwnTracks::ExportParser do
describe '#call' do
subject(:parser) { described_class.new(file_path, import_id).call }
let(:file_path) { 'spec/fixtures/owntracks_export.json' }
let(:import_id) { nil }
context 'when file exists' do
it 'creates points' do
expect { parser }.to change { Point.count }.by(8)
end
end
context 'when file does not exist' do
let(:file_path) { 'spec/fixtures/not_found.json' }
it 'raises error' do
expect { parser }.to raise_error('File not found')
end
end
end
end

View file

@ -0,0 +1,194 @@
require 'rails_helper'
RSpec.describe OwnTracks::Params do
describe '#call' do
subject(:params) { described_class.new(raw_point_params).call }
let(:file_path) { 'spec/fixtures/owntracks_export.json' }
let(:file) { File.open(file_path) }
let(:json) { JSON.parse(file.read) }
let(:user) { json.keys.first }
let(:topic) { json[user].keys.first }
let(:raw_point_params) { json[user][topic].first }
let(:expected_json) do
{
latitude: 40.7128,
longitude: -74.006,
battery_status: 'unknown',
battery: 85,
ping: nil,
altitude: 41,
accuracy: 8,
vertical_accuracy: 3,
velocity: nil,
connection: 'wifi',
ssid: 'Home Wifi',
bssid: 'b0:f2:8:45:94:33',
trigger: 'unknown',
tracker_id: 'RO',
timestamp: 1706965203,
inrids: ['5f1d1b'],
in_regions: ['home'],
topic: 'owntracks/test/iPhone 12 Pro',
raw_data: {
'batt'=>85,
'lon'=>-74.006,
'acc'=>8,
'bs'=>2,
'inrids'=>['5f1d1b'],
'BSSID'=>'b0:f2:8:45:94:33',
'SSID'=>'Home Wifi',
'vac'=>3,
'inregions'=>['home'],
'lat'=>40.7128,
'topic'=>'owntracks/test/iPhone 12 Pro',
't'=>'p',
'conn'=>'w',
'm'=>1,
'tst'=>1706965203,
'alt'=>41,
'_type'=>'location',
'tid'=>'RO',
'_http'=>true,
'ghash'=>'u33d773',
'isorcv'=>'2024-02-03T13:00:03Z',
'isotst'=>'2024-02-03T13:00:03Z',
'disptst'=>'2024-02-03 13:00:03'
}
}
end
it 'returns parsed params' do
expect(params).to eq(expected_json)
end
context 'when battery status is unplugged' do
let(:raw_point_params) { super().merge(bs: 'u') }
it 'returns parsed params' do
expect(params[:battery_status]).to eq('unplugged')
end
end
context 'when battery status is charging' do
let(:raw_point_params) { super().merge(bs: 'c') }
it 'returns parsed params' do
expect(params[:battery_status]).to eq('charging')
end
end
context 'when battery status is full' do
let(:raw_point_params) { super().merge(bs: 'f') }
it 'returns parsed params' do
expect(params[:battery_status]).to eq('full')
end
end
context 'when trigger is background_event' do
let(:raw_point_params) { super().merge(m: 'p') }
it 'returns parsed params' do
expect(params[:trigger]).to eq('background_event')
end
end
context 'when trigger is circular_region_event' do
let(:raw_point_params) { super().merge(m: 'c') }
it 'returns parsed params' do
expect(params[:trigger]).to eq('circular_region_event')
end
end
context 'when trigger is beacon_event' do
let(:raw_point_params) { super().merge(m: 'b') }
it 'returns parsed params' do
expect(params[:trigger]).to eq('beacon_event')
end
end
context 'when trigger is report_location_message_event' do
let(:raw_point_params) { super().merge(m: 'r') }
it 'returns parsed params' do
expect(params[:trigger]).to eq('report_location_message_event')
end
end
context 'when trigger is manual_event' do
let(:raw_point_params) { super().merge(m: 'u') }
it 'returns parsed params' do
expect(params[:trigger]).to eq('manual_event')
end
end
context 'when trigger is timer_based_event' do
let(:raw_point_params) { super().merge(m: 't') }
it 'returns parsed params' do
expect(params[:trigger]).to eq('timer_based_event')
end
end
context 'when trigger is settings_monitoring_event' do
let(:raw_point_params) { super().merge(m: 'v') }
it 'returns parsed params' do
expect(params[:trigger]).to eq('settings_monitoring_event')
end
end
context 'when connection is mobile' do
let(:raw_point_params) { super().merge(conn: 'm') }
it 'returns parsed params' do
expect(params[:connection]).to eq('mobile')
end
end
context 'when connection is wifi' do
let(:raw_point_params) { super().merge(conn: 'w') }
it 'returns parsed params' do
expect(params[:connection]).to eq('wifi')
end
end
context 'when connection is offline' do
let(:raw_point_params) { super().merge(conn: 'o') }
it 'returns parsed params' do
expect(params[:connection]).to eq('offline')
end
end
context 'when connection is unknown' do
let(:raw_point_params) { super().merge(conn: 'unknown') }
it 'returns parsed params' do
expect(params[:connection]).to eq('unknown')
end
end
context 'when battery status is unknown' do
let(:raw_point_params) { super().merge(bs: 'unknown') }
it 'returns parsed params' do
expect(params[:battery_status]).to eq('unknown')
end
end
context 'when trigger is unknown' do
let(:raw_point_params) { super().merge(m: 'unknown') }
it 'returns parsed params' do
expect(params[:trigger]).to eq('unknown')
end
end
end
end