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 %> +
+ + + + + + + + + + + <% @imports.each do |import| %> + + + + + + <% end %> + +
NamePointsCreated at
+ <%= link_to import.name, import, class: 'underline hover:no-underline' %> (<%= import.source %>) + <%= import.points.count %><%= import.created_at.strftime("%d.%m.%Y, %H:%M") %>
+
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