diff --git a/Gemfile b/Gemfile
index 4a1d6194..ceefd493 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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
diff --git a/Gemfile.lock b/Gemfile.lock
index 5edbe58a..25805348 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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
diff --git a/app/controllers/api/v1/points_controller.rb b/app/controllers/api/v1/points_controller.rb
index 1a1e8b8f..aa1e280b 100644
--- a/app/controllers/api/v1/points_controller.rb
+++ b/app/controllers/api/v1/points_controller.rb
@@ -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
diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb
index 184e7ff6..20f556f7 100644
--- a/app/controllers/imports_controller.rb
+++ b/app/controllers/imports_controller.rb
@@ -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
diff --git a/app/controllers/points_controller.rb b/app/controllers/points_controller.rb
index 5b439ef8..efd8d9ab 100644
--- a/app/controllers/points_controller.rb
+++ b/app/controllers/points_controller.rb
@@ -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)
diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js
index e282a652..ac495d21 100644
--- a/app/javascript/controllers/maps_controller.js
+++ b/app/javascript/controllers/maps_controller.js
@@ -30,6 +30,7 @@ export default class extends Controller {
Altitude: ${marker[3]}m
Velocity: ${marker[5]}km/h
Battery: ${marker[2]}%
+ id: ${marker[6]}
`;
}
@@ -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,
diff --git a/app/services/own_tracks/params.rb b/app/services/own_tracks/params.rb
index b37abe77..350283f7 100644
--- a/app/services/own_tracks/params.rb
+++ b/app/services/own_tracks/params.rb
@@ -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
diff --git a/app/views/imports/index.html.erb b/app/views/imports/index.html.erb
index bbf0c6c5..40a3d12c 100644
--- a/app/views/imports/index.html.erb
+++ b/app/views/imports/index.html.erb
@@ -5,6 +5,28 @@
- <%= render @imports %>
+
+
+
+
+
+ | Name |
+ Points |
+ Created at |
+
+
+
+ <% @imports.each do |import| %>
+
+ |
+ <%= link_to import.name, import, class: 'underline hover:no-underline' %> (<%= import.source %>)
+ |
+ <%= import.points.count %> |
+ <%= import.created_at.strftime("%d.%m.%Y, %H:%M") %> |
+
+ <% end %>
+
+
+
diff --git a/app/views/imports/show.html.erb b/app/views/imports/show.html.erb
index 83dc2e69..2f5d28c9 100644
--- a/app/views/imports/show.html.erb
+++ b/app/views/imports/show.html.erb
@@ -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" %>
- <%= 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" %>
<%= link_to "Back to imports", imports_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
diff --git a/spec/fixtures/owntracks_export.json b/spec/fixtures/owntracks_export.json
new file mode 100644
index 00000000..55996056
--- /dev/null
+++ b/spec/fixtures/owntracks_export.json
@@ -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"
+ }
+ ]
+ }
+}
+
diff --git a/spec/services/own_tracks/export_parser_spec.rb b/spec/services/own_tracks/export_parser_spec.rb
new file mode 100644
index 00000000..bffb0d76
--- /dev/null
+++ b/spec/services/own_tracks/export_parser_spec.rb
@@ -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
diff --git a/spec/services/own_tracks/params_spec.rb b/spec/services/own_tracks/params_spec.rb
new file mode 100644
index 00000000..42188694
--- /dev/null
+++ b/spec/services/own_tracks/params_spec.rb
@@ -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