mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Implement bulk points deletion
This commit is contained in:
parent
22c8a210b1
commit
814095a4a2
39 changed files with 610 additions and 118 deletions
|
|
@ -1 +1 @@
|
||||||
0.2.5
|
0.3.0
|
||||||
|
|
|
||||||
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -5,12 +5,32 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [0.3.0] — 2024-05-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add Points page to display all the points as a table with pagination to allow users to delete points
|
||||||
|
- Sidekiq web interface to monitor background jobs is now available at `/sidekiq`
|
||||||
|
- Now you can choose a date range of points to be exported
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Stop selecting `raw_data` column during requests to `imports` and `points` tables to improve performance.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Rename PointsController to MapController along with all the views and routes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.2.5] — 2024-05-21
|
## [0.2.5] — 2024-05-21
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Stop ignoring `raw_data` column during requests to `imports` and `points` tables. This was preventing points from being created.
|
- Stop ignoring `raw_data` column during requests to `imports` and `points` tables. This was preventing points from being created.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.2.4] — 2024-05-19
|
## [0.2.4] — 2024-05-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
3
Gemfile
3
Gemfile
|
|
@ -9,6 +9,7 @@ gem 'chartkick'
|
||||||
gem 'devise'
|
gem 'devise'
|
||||||
gem 'geocoder'
|
gem 'geocoder'
|
||||||
gem 'importmap-rails'
|
gem 'importmap-rails'
|
||||||
|
gem 'oj'
|
||||||
gem 'pg'
|
gem 'pg'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
gem 'pundit'
|
gem 'pundit'
|
||||||
|
|
@ -23,6 +24,7 @@ gem 'stimulus-rails'
|
||||||
gem 'tailwindcss-rails'
|
gem 'tailwindcss-rails'
|
||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
|
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
|
||||||
|
gem 'will_paginate', '~> 4.0'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'debug', platforms: %i[mri mingw x64_mingw]
|
gem 'debug', platforms: %i[mri mingw x64_mingw]
|
||||||
|
|
@ -36,6 +38,7 @@ group :development, :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
gem 'fakeredis'
|
||||||
gem 'shoulda-matchers'
|
gem 'shoulda-matchers'
|
||||||
gem 'simplecov'
|
gem 'simplecov'
|
||||||
gem 'super_diff'
|
gem 'super_diff'
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ GEM
|
||||||
factory_bot_rails (6.4.3)
|
factory_bot_rails (6.4.3)
|
||||||
factory_bot (~> 6.4)
|
factory_bot (~> 6.4)
|
||||||
railties (>= 5.0.0)
|
railties (>= 5.0.0)
|
||||||
|
fakeredis (0.1.4)
|
||||||
ffaker (2.23.0)
|
ffaker (2.23.0)
|
||||||
foreman (0.88.1)
|
foreman (0.88.1)
|
||||||
fugit (1.10.1)
|
fugit (1.10.1)
|
||||||
|
|
@ -184,6 +185,8 @@ GEM
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.5-x86_64-linux)
|
nokogiri (1.16.5-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
|
oj (3.16.3)
|
||||||
|
bigdecimal (>= 3.0)
|
||||||
optimist (3.1.0)
|
optimist (3.1.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
parallel (1.24.0)
|
parallel (1.24.0)
|
||||||
|
|
@ -374,6 +377,7 @@ GEM
|
||||||
websocket-driver (0.7.6)
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
|
will_paginate (4.0.0)
|
||||||
zeitwerk (2.6.14)
|
zeitwerk (2.6.14)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
|
@ -391,10 +395,12 @@ DEPENDENCIES
|
||||||
devise
|
devise
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
factory_bot_rails
|
factory_bot_rails
|
||||||
|
fakeredis
|
||||||
ffaker
|
ffaker
|
||||||
foreman
|
foreman
|
||||||
geocoder
|
geocoder
|
||||||
importmap-rails
|
importmap-rails
|
||||||
|
oj
|
||||||
pg
|
pg
|
||||||
pry-byebug
|
pry-byebug
|
||||||
pry-rails
|
pry-rails
|
||||||
|
|
@ -419,6 +425,7 @@ DEPENDENCIES
|
||||||
turbo-rails
|
turbo-rails
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
webmock
|
webmock
|
||||||
|
will_paginate (~> 4.0)
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 3.2.3p157
|
ruby 3.2.3p157
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -4,21 +4,48 @@ class ExportController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@export = current_user.export_data
|
@start_at = Time.zone.at(start_at)
|
||||||
|
@end_at = Time.zone.at(end_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
def download
|
def download
|
||||||
export = current_user.export_data
|
export = current_user.export_data(start_at:, end_at:)
|
||||||
|
|
||||||
send_data export, filename:
|
send_data export, filename:, type: 'applocation/json', disposition: 'attachment'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def filename
|
def filename
|
||||||
first_point_datetime = Time.zone.at(current_user.points.first.timestamp).to_s
|
first_point_datetime = Time.zone.at(start_at).to_s
|
||||||
last_point_datetime = Time.zone.at(current_user.points.last.timestamp).to_s
|
last_point_datetime = Time.zone.at(end_at).to_s
|
||||||
|
|
||||||
"dawarich-export-#{first_point_datetime}-#{last_point_datetime}.json".gsub(' ', '_')
|
"dawarich-export-#{first_point_datetime}-#{last_point_datetime}.json".gsub(' ', '_')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def start_at
|
||||||
|
first_point_timestamp = Point.order(timestamp: :asc)&.first&.timestamp
|
||||||
|
|
||||||
|
@start_at ||=
|
||||||
|
if params[:start_at].nil? && first_point_timestamp.present?
|
||||||
|
first_point_timestamp
|
||||||
|
elsif params[:start_at].nil?
|
||||||
|
1.month.ago.to_i
|
||||||
|
else
|
||||||
|
Time.zone.parse(params[:start_at]).to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_at
|
||||||
|
last_point_timestamp = Point.order(timestamp: :desc)&.last&.timestamp
|
||||||
|
|
||||||
|
@end_at ||=
|
||||||
|
if params[:end_at].nil? && last_point_timestamp.present?
|
||||||
|
last_point_timestamp
|
||||||
|
elsif params[:end_at].nil?
|
||||||
|
Time.zone.now.to_i
|
||||||
|
else
|
||||||
|
Time.zone.parse(params[:end_at]).to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class HomeController < ApplicationController
|
class HomeController < ApplicationController
|
||||||
def index
|
def index
|
||||||
if current_user
|
redirect_to map_url if current_user
|
||||||
redirect_to points_url
|
|
||||||
end
|
|
||||||
|
|
||||||
@points = current_user.points if current_user
|
@points = current_user.points.without_raw_data if current_user
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
41
app/controllers/map_controller.rb
Normal file
41
app/controllers/map_controller.rb
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class MapController < ApplicationController
|
||||||
|
before_action :authenticate_user!
|
||||||
|
|
||||||
|
def index
|
||||||
|
@points = Point.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at, end_at).order(timestamp: :asc)
|
||||||
|
|
||||||
|
@countries_and_cities = CountriesAndCities.new(@points).call
|
||||||
|
@coordinates =
|
||||||
|
@points.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.zone.at(start_at)
|
||||||
|
@end_at = Time.zone.at(end_at)
|
||||||
|
@years = (@start_at.year..@end_at.year).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def start_at
|
||||||
|
return 1.month.ago.beginning_of_day.to_i if params[:start_at].nil?
|
||||||
|
|
||||||
|
Time.zone.parse(params[:start_at]).to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def end_at
|
||||||
|
return Time.zone.today.end_of_day.to_i if params[:end_at].nil?
|
||||||
|
|
||||||
|
Time.zone.parse(params[:end_at]).to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def distance
|
||||||
|
@distance ||= 0
|
||||||
|
|
||||||
|
@coordinates.each_cons(2) do
|
||||||
|
@distance += Geocoder::Calculations.distance_between([_1[0], _1[1]], [_2[0], _2[1]], units: :km)
|
||||||
|
end
|
||||||
|
|
||||||
|
@distance.round(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,22 +1,32 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PointsController < ApplicationController
|
class PointsController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@points = Point.where('timestamp >= ? AND timestamp <= ?', start_at, end_at).order(timestamp: :asc)
|
@points =
|
||||||
|
Point
|
||||||
|
.without_raw_data
|
||||||
|
.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
|
||||||
|
.order(timestamp: :asc)
|
||||||
|
.paginate(page: params[:page], per_page: 50)
|
||||||
|
|
||||||
@countries_and_cities = CountriesAndCities.new(@points).call
|
|
||||||
@coordinates =
|
|
||||||
@points.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.zone.at(start_at)
|
@start_at = Time.zone.at(start_at)
|
||||||
@end_at = Time.zone.at(end_at)
|
@end_at = Time.zone.at(end_at)
|
||||||
@years = (@start_at.year..@end_at.year).to_a
|
end
|
||||||
|
|
||||||
|
def bulk_destroy
|
||||||
|
Point.where(id: params[:point_ids].compact).destroy_all
|
||||||
|
|
||||||
|
redirect_to points_url, notice: "Points were successfully destroyed.", status: :see_other
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def point_params
|
||||||
|
params.fetch(:point, {})
|
||||||
|
end
|
||||||
|
|
||||||
def start_at
|
def start_at
|
||||||
return 1.month.ago.beginning_of_day.to_i if params[:start_at].nil?
|
return 1.month.ago.beginning_of_day.to_i if params[:start_at].nil?
|
||||||
|
|
||||||
|
|
@ -28,14 +38,4 @@ class PointsController < ApplicationController
|
||||||
|
|
||||||
Time.zone.parse(params[:end_at]).to_i
|
Time.zone.parse(params[:end_at]).to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
def distance
|
|
||||||
@distance ||= 0
|
|
||||||
|
|
||||||
@coordinates.each_cons(2) do
|
|
||||||
@distance += Geocoder::Calculations.distance_between([_1[0], _1[1]], [_2[0], _2[1]], units: :km)
|
|
||||||
end
|
|
||||||
|
|
||||||
@distance.round(1)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -87,4 +87,8 @@ module ApplicationHelper
|
||||||
|
|
||||||
"(#{points_pluralized})"
|
"(#{points_pluralized})"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def active_class?(link_path)
|
||||||
|
'btn-active' if current_page?(link_path)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
module ExportHelper
|
|
||||||
end
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
module HomeHelper
|
|
||||||
end
|
|
||||||
|
|
@ -4,7 +4,7 @@ module PointsHelper
|
||||||
def link_to_date(timestamp)
|
def link_to_date(timestamp)
|
||||||
datetime = Time.zone.at(timestamp)
|
datetime = Time.zone.at(timestamp)
|
||||||
|
|
||||||
link_to points_path(start_at: datetime.beginning_of_day, end_at: datetime.end_of_day), \
|
link_to map_path(start_at: datetime.beginning_of_day, end_at: datetime.end_of_day), \
|
||||||
class: 'underline hover:no-underline' do
|
class: 'underline hover:no-underline' do
|
||||||
datetime.strftime('%d.%m.%Y')
|
datetime.strftime('%d.%m.%Y')
|
||||||
end
|
end
|
||||||
|
|
|
||||||
29
app/javascript/controllers/checkbox_select_all_controller.js
Normal file
29
app/javascript/controllers/checkbox_select_all_controller.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
// Connects to data-controller="checkbox-select-all"
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["parent", "child"]
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.parentTarget.checked = false
|
||||||
|
this.childTargets.map(x => x.checked = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleChildren() {
|
||||||
|
if (this.parentTarget.checked) {
|
||||||
|
this.childTargets.map(x => x.checked = true)
|
||||||
|
console.log('toggleChildrenChecked')
|
||||||
|
} else {
|
||||||
|
this.childTargets.map(x => x.checked = false)
|
||||||
|
console.log('toggleChildrenUNChecked')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleParent() {
|
||||||
|
if (this.childTargets.map(x => x.checked).includes(false)) {
|
||||||
|
this.parentTarget.checked = false
|
||||||
|
} else {
|
||||||
|
this.parentTarget.checked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Point < ApplicationRecord
|
class Point < ApplicationRecord
|
||||||
# self.ignored_columns = %w[raw_data]
|
# self.ignored_columns = %w[raw_data]
|
||||||
|
|
||||||
|
|
@ -15,6 +17,14 @@ class Point < ApplicationRecord
|
||||||
|
|
||||||
after_create :async_reverse_geocode
|
after_create :async_reverse_geocode
|
||||||
|
|
||||||
|
def self.without_raw_data
|
||||||
|
select(column_names - ['raw_data'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def recorded_at
|
||||||
|
Time.zone.at(timestamp)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def async_reverse_geocode
|
def async_reverse_geocode
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class Stat < ApplicationRecord
|
||||||
end_of_day = day.end_of_day.to_i
|
end_of_day = day.end_of_day.to_i
|
||||||
|
|
||||||
# We have to filter by user as well
|
# We have to filter by user as well
|
||||||
points = Point.where(timestamp: beginning_of_day..end_of_day)
|
points = Point.without_raw_data.where(timestamp: beginning_of_day..end_of_day)
|
||||||
|
|
||||||
data = { day: index, distance: 0 }
|
data = { day: index, distance: 0 }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,10 @@ class User < ApplicationRecord
|
||||||
|
|
||||||
after_create :create_api_key
|
after_create :create_api_key
|
||||||
|
|
||||||
def export_data
|
def export_data(start_at: nil, end_at: nil)
|
||||||
::ExportSerializer.new(points, email).call
|
geopoints = time_framed_points(start_at, end_at)
|
||||||
|
|
||||||
|
::ExportSerializer.new(geopoints, email).call
|
||||||
end
|
end
|
||||||
|
|
||||||
def countries_visited
|
def countries_visited
|
||||||
|
|
@ -46,13 +48,26 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_reverse_geocoded
|
def total_reverse_geocoded
|
||||||
points.where.not(country: nil, city: nil).count
|
points.select(:id).where.not(country: nil, city: nil).count
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def create_api_key
|
def create_api_key
|
||||||
self.api_key = SecureRandom.hex(16)
|
self.api_key = SecureRandom.hex(16)
|
||||||
|
|
||||||
save
|
save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def time_framed_points(start_at, end_at)
|
||||||
|
return points.without_raw_data if start_at.nil? && end_at.nil?
|
||||||
|
|
||||||
|
if start_at && end_at
|
||||||
|
points.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
|
||||||
|
elsif start_at
|
||||||
|
points.without_raw_data.where('timestamp >= ?', start_at)
|
||||||
|
elsif end_at
|
||||||
|
points.without_raw_data.where('timestamp <= ?', end_at)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ class CreateStats
|
||||||
|
|
||||||
def points(beginning_of_month_timestamp, end_of_month_timestamp)
|
def points(beginning_of_month_timestamp, end_of_month_timestamp)
|
||||||
Point
|
Point
|
||||||
|
.without_raw_data
|
||||||
.where(timestamp: beginning_of_month_timestamp..end_of_month_timestamp)
|
.where(timestamp: beginning_of_month_timestamp..end_of_month_timestamp)
|
||||||
.order(:timestamp)
|
.order(:timestamp)
|
||||||
.select(:latitude, :longitude, :timestamp, :city, :country)
|
.select(:latitude, :longitude, :timestamp, :city, :country)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,32 @@
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class='m-5'>
|
<div class='m-5'>
|
||||||
<h1 class='text-3xl font-bold'>Export Data</h1>
|
<h1 class='text-3xl font-bold'>Export Data</h1>
|
||||||
<%= link_to 'Download JSON', export_download_path, class: 'btn btn-primary my-5' %>
|
|
||||||
|
<div role="alert" class="alert alert-info my-5">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||||
|
<span>Default selected timeframes are based on first and last geopoint timestamps</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= form_with url: export_download_path, method: :get, data: { turbo: false } do |f| %>
|
||||||
|
<div class="flex flex-col md:flex-row md:space-x-4 md:items-end">
|
||||||
|
<div class="w-full md:w-1/3">
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
<%= f.label :start_at, class: "text-sm font-semibold" %>
|
||||||
|
<%= f.datetime_local_field :start_at, class: "rounded-md w-full", value: @start_at %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-1/3">
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
<%= f.label :end_at, class: "text-sm font-semibold" %>
|
||||||
|
<%= f.datetime_local_field :end_at, class: "rounded-md w-full", value: @end_at %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-1/3">
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
<%= f.submit "Download JSON", class: "px-4 py-2 bg-blue-500 text-white rounded-md" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<%= form_with(model: import, class: "contents") do |form| %>
|
<%= form_with model: import, class: "contents" do |form| %>
|
||||||
<% if import.errors.any? %>
|
<% if import.errors.any? %>
|
||||||
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
||||||
<h2><%= pluralize(import.errors.count, "error") %> prohibited this import from being saved:</h2>
|
<h2><%= pluralize(import.errors.count, "error") %> prohibited this import from being saved:</h2>
|
||||||
|
|
|
||||||
39
app/views/map/index.html.erb
Normal file
39
app/views/map/index.html.erb
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<div class='w-4/5 mt-10'>
|
||||||
|
<div class="flex flex-col space-y-4 mb-4 w-full">
|
||||||
|
<%= form_with url: map_path, method: :get do |f| %>
|
||||||
|
<div class="flex flex-col md:flex-row md:space-x-4 md:items-end">
|
||||||
|
<div class="w-full md:w-1/3">
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
<%= f.label :start_at, class: "text-sm font-semibold" %>
|
||||||
|
<%= f.datetime_local_field :start_at, class: "rounded-md w-full", value: @start_at %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-1/3">
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
<%= f.label :end_at, class: "text-sm font-semibold" %>
|
||||||
|
<%= f.datetime_local_field :end_at, class: "rounded-md w-full", value: @end_at %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-1/3">
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
<%= f.submit "Search", class: "px-4 py-2 bg-blue-500 text-white rounded-md" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-full"
|
||||||
|
data-controller="maps"
|
||||||
|
data-coordinates="<%= @coordinates %>"
|
||||||
|
data-center="<%= MAP_CENTER %>">
|
||||||
|
<div data-maps-target="container" class="h-[25rem] w-auto min-h-screen"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class='w-1/5 mt-10'>
|
||||||
|
<%= render 'shared/right_sidebar' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
17
app/views/points/_form.html.erb
Normal file
17
app/views/points/_form.html.erb
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<%= form_with(model: point, class: "contents") do |form| %>
|
||||||
|
<% if point.errors.any? %>
|
||||||
|
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
||||||
|
<h2><%= pluralize(point.errors.count, "error") %> prohibited this point from being saved:</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<% point.errors.each do |error| %>
|
||||||
|
<li><%= error.full_message %></li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="inline">
|
||||||
|
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
19
app/views/points/_point.html.erb
Normal file
19
app/views/points/_point.html.erb
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<tr id="<%= dom_id point %>" class='hover'>
|
||||||
|
<td>
|
||||||
|
<%= check_box_tag "point_ids[]",
|
||||||
|
point.id,
|
||||||
|
nil,
|
||||||
|
{
|
||||||
|
multiple: true,
|
||||||
|
form: :bulk_destroy_form,
|
||||||
|
data: {
|
||||||
|
checkbox_select_all_target: 'child',
|
||||||
|
action: 'change->checkbox-select-all#toggleParent'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</td>
|
||||||
|
<td><%= point.recorded_at %></td>
|
||||||
|
<td><%= point.latitude %>, <%= point.longitude %></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
8
app/views/points/edit.html.erb
Normal file
8
app/views/points/edit.html.erb
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
|
<h1 class="font-bold text-4xl">Editing point</h1>
|
||||||
|
|
||||||
|
<%= render "form", point: @point %>
|
||||||
|
|
||||||
|
<%= link_to "Show this point", @point, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
|
<%= link_to "Back to points", points_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
|
</div>
|
||||||
|
|
@ -1,39 +1,63 @@
|
||||||
<div class='w-4/5 mt-10'>
|
<% content_for :title, "Points" %>
|
||||||
<div class="flex flex-col space-y-4 mb-4 w-full">
|
|
||||||
<%= form_with url: points_path, method: :get do |f| %>
|
<div class="w-full">
|
||||||
<div class="flex flex-col md:flex-row md:space-x-4 md:items-end">
|
<%= form_with url: points_path, method: :get do |f| %>
|
||||||
<div class="w-full md:w-1/3">
|
<div class="flex flex-col md:flex-row md:space-x-4 md:items-end">
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="w-full md:w-1/3">
|
||||||
<%= f.label :start_at, class: "text-sm font-semibold" %>
|
<div class="flex flex-col space-y-2">
|
||||||
<%= f.datetime_local_field :start_at, class: "rounded-md w-full", value: @start_at %>
|
<%= f.label :start_at, class: "text-sm font-semibold" %>
|
||||||
</div>
|
<%= f.datetime_local_field :start_at, class: "rounded-md w-full", value: @start_at %>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/3">
|
</div>
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="w-full md:w-1/3">
|
||||||
<%= f.label :end_at, class: "text-sm font-semibold" %>
|
<div class="flex flex-col space-y-2">
|
||||||
<%= f.datetime_local_field :end_at, class: "rounded-md w-full", value: @end_at %>
|
<%= f.label :end_at, class: "text-sm font-semibold" %>
|
||||||
</div>
|
<%= f.datetime_local_field :end_at, class: "rounded-md w-full", value: @end_at %>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/3">
|
</div>
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="w-full md:w-1/3">
|
||||||
<%= f.submit "Search", class: "px-4 py-2 bg-blue-500 text-white rounded-md" %>
|
<div class="flex flex-col space-y-2">
|
||||||
</div>
|
<%= f.submit "Search", class: "px-4 py-2 bg-blue-500 text-white rounded-md" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="w-full"
|
|
||||||
data-controller="maps"
|
|
||||||
data-coordinates="<%= @coordinates %>"
|
|
||||||
data-center="<%= MAP_CENTER %>">
|
|
||||||
<div data-maps-target="container" class="h-[25rem] w-auto min-h-screen"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class='text-center my-5'>
|
||||||
|
<%= will_paginate @points %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="points" class="min-w-full">
|
||||||
|
<div data-controller='checkbox-select-all'>
|
||||||
|
<%= form_with url: bulk_destroy_points_path, method: :delete, id: :bulk_destroy_form do |f| %>
|
||||||
|
<%= f.submit "Delete Selected", class: "px-4 py-2 bg-red-500 text-white rounded-md", data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" } %>
|
||||||
|
<table class='table'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<%= label_tag do %>
|
||||||
|
Select all
|
||||||
|
<%= check_box_tag 'Select all',
|
||||||
|
id: :select_all_points,
|
||||||
|
data: {
|
||||||
|
checkbox_select_all_target: 'parent',
|
||||||
|
action: 'change->checkbox-select-all#toggleChildren'
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th>Recorded At</th>
|
||||||
|
<th>Coordinates</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @points.each do |point| %>
|
||||||
|
<%= render point %>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='w-1/5 mt-10'>
|
|
||||||
<%= render 'shared/right_sidebar' %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
7
app/views/points/new.html.erb
Normal file
7
app/views/points/new.html.erb
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
|
<h1 class="font-bold text-4xl">New point</h1>
|
||||||
|
|
||||||
|
<%= render "form", point: @point %>
|
||||||
|
|
||||||
|
<%= link_to "Back to points", points_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
|
</div>
|
||||||
15
app/views/points/show.html.erb
Normal file
15
app/views/points/show.html.erb
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<div class="mx-auto md:w-2/3 w-full flex">
|
||||||
|
<div class="mx-auto">
|
||||||
|
<% if notice.present? %>
|
||||||
|
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= render @point %>
|
||||||
|
|
||||||
|
<%= link_to "Edit this point", edit_point_path(@point), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
|
<%= link_to "Back to points", points_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||||
|
<div class="inline-block ml-2">
|
||||||
|
<%= button_to "Destroy this point", @point, method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -5,9 +5,10 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" /></svg>
|
||||||
</label>
|
</label>
|
||||||
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
|
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
|
||||||
<li><%= link_to 'Points', points_url %></li>
|
<li><%= link_to 'Map', map_url, class: "#{active_class?(map_url)}" %></li>
|
||||||
<li><%= link_to 'Stats', stats_url %></li>
|
<li><%= link_to 'Points', points_url, class: "#{active_class?(points_url)}" %></li>
|
||||||
<li><%= link_to 'Imports', imports_url %></li>
|
<li><%= link_to 'Stats', stats_url, class: "#{active_class?(stats_url)}" %></li>
|
||||||
|
<li><%= link_to 'Imports', imports_url, class: "#{active_class?(imports_url)}" %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<%= link_to 'DaWarIch', root_path, class: 'btn btn-ghost normal-case text-xl'%>
|
<%= link_to 'DaWarIch', root_path, class: 'btn btn-ghost normal-case text-xl'%>
|
||||||
|
|
@ -36,9 +37,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-center hidden lg:flex">
|
<div class="navbar-center hidden lg:flex">
|
||||||
<ul class="menu menu-horizontal px-1">
|
<ul class="menu menu-horizontal px-1">
|
||||||
<li><%= link_to 'Points', points_url %></li>
|
<li><%= link_to 'Map', map_url, class: "#{active_class?(map_url)}" %></li>
|
||||||
<li><%= link_to 'Stats', stats_url %></li>
|
<li><%= link_to 'Points', points_url, class: "#{active_class?(points_url)}" %></li>
|
||||||
<li><%= link_to 'Imports', imports_url %></li>
|
<li><%= link_to 'Stats', stats_url, class: "#{active_class?(stats_url)}" %></li>
|
||||||
|
<li><%= link_to 'Imports', imports_url, class: "#{active_class?(imports_url)}" %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<div tabindex="0" role="button" class="btn m-1">Select year</div>
|
<div tabindex="0" role="button" class="btn m-1">Select year</div>
|
||||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||||
<% Stat.years.each do |year| %>
|
<% Stat.years.each do |year| %>
|
||||||
<li><%= link_to year, points_url(year_timespan(year).merge(year: year)) %></li>
|
<li><%= link_to year, map_url(year_timespan(year).merge(year: year)) %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<% (1..12).to_a.each_slice(3) do |months| %>
|
<% (1..12).to_a.each_slice(3) do |months| %>
|
||||||
<% months.each do |month_number| %>
|
<% months.each do |month_number| %>
|
||||||
<% if past?(year, month_number) && points_exist?(year, month_number) %>
|
<% if past?(year, month_number) && points_exist?(year, month_number) %>
|
||||||
<%= link_to Date::ABBR_MONTHNAMES[month_number], points_url(timespan(month_number, year)), class: 'btn btn-default' %>
|
<%= link_to Date::ABBR_MONTHNAMES[month_number], map_url(timespan(month_number, year)), class: 'btn btn-default' %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class='btn btn-disabled'><%= Date::ABBR_MONTHNAMES[month_number] %></div>
|
<div class='btn btn-disabled'><%= Date::ABBR_MONTHNAMES[month_number] %></div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<div id="<%= dom_id stat %>" class="card w-full bg-base-200 shadow-xl">
|
<div id="<%= dom_id stat %>" class="card w-full bg-base-200 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title">
|
<h2 class="card-title">
|
||||||
<%= link_to points_url(month_timespan(stat)), class: "underline hover:no-underline text-#{header_colors.sample}" do %>
|
<%= link_to map_url(month_timespan(stat)), class: "underline hover:no-underline text-#{header_colors.sample}" do %>
|
||||||
<%= "#{Date::MONTHNAMES[stat.month]} of #{stat.year}" %>
|
<%= "#{Date::MONTHNAMES[stat.month]} of #{stat.year}" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<h2 class='text-3xl font-bold mt-10'>
|
<h2 class='text-3xl font-bold mt-10'>
|
||||||
<%= link_to year, "/stats/#{year}", class: "underline hover:no-underline text-#{header_colors.sample}" %>
|
<%= link_to year, "/stats/#{year}", class: "underline hover:no-underline text-#{header_colors.sample}" %>
|
||||||
<%= link_to '[Map]', points_url(year_timespan(year)), class: 'underline hover:no-underline' %>
|
<%= link_to '[Map]', map_url(year_timespan(year)), class: 'underline hover:no-underline' %>
|
||||||
</h2>
|
</h2>
|
||||||
<div class='my-10'>
|
<div class='my-10'>
|
||||||
<%= column_chart(
|
<%= column_chart(
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<div class="stat text-center">
|
<div class="stat text-center">
|
||||||
<div class="stat-value text-success">
|
<div class="stat-value text-success">
|
||||||
<%= number_with_delimiter current_user.points.count %>
|
<%= number_with_delimiter current_user.points.without_raw_data.count(:id) %>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Geopoints tracked</div>
|
<div class="stat-title">Geopoints tracked</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
<div class="stat-title">Cities visited</div>
|
<div class="stat-title">Cities visited</div>
|
||||||
<dialog id="cities_visited" class="modal">
|
<dialog id="cities_visited" class="modal">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
<h3 class="font-bold text-lg">Countries visited</h3>
|
<h3 class="font-bold text-lg">Cities visited</h3>
|
||||||
<p class="py-4">
|
<p class="py-4">
|
||||||
<% current_user.cities_visited.each do |city| %>
|
<% current_user.cities_visited.each do |city| %>
|
||||||
<p><%= city %></p>
|
<p><%= city %></p>
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-<%= header_colors.sample %>">
|
<h2 class="card-title text-<%= header_colors.sample %>">
|
||||||
<%= link_to year, "/stats/#{year}", class: 'underline hover:no-underline' %>
|
<%= link_to year, "/stats/#{year}", class: 'underline hover:no-underline' %>
|
||||||
<%= link_to '[Map]', points_url(year_timespan(year)), class: 'underline hover:no-underline' %>
|
<%= link_to '[Map]', map_url(year_timespan(year)), class: 'underline hover:no-underline' %>
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
<% cache [current_user, 'year_distance_stat_in_km', year], skip_digest: true do %>
|
<% cache [current_user, 'year_distance_stat_in_km', year], skip_digest: true do %>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,21 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'sidekiq/web'
|
||||||
|
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
mount Rswag::Api::Engine => '/api-docs'
|
mount Rswag::Api::Engine => '/api-docs'
|
||||||
mount Rswag::Ui::Engine => '/api-docs'
|
mount Rswag::Ui::Engine => '/api-docs'
|
||||||
|
mount Sidekiq::Web => '/sidekiq'
|
||||||
get 'settings/theme', to: 'settings#theme'
|
get 'settings/theme', to: 'settings#theme'
|
||||||
get 'export', to: 'export#index'
|
get 'export', to: 'export#index'
|
||||||
get 'export/download', to: 'export#download'
|
get 'export/download', to: 'export#download'
|
||||||
|
|
||||||
resources :imports
|
resources :imports
|
||||||
|
resources :points, only: %i[index] do
|
||||||
|
collection do
|
||||||
|
delete :bulk_destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
resources :stats, only: :index do
|
resources :stats, only: :index do
|
||||||
collection do
|
collection do
|
||||||
post :update
|
post :update
|
||||||
|
|
@ -20,7 +28,7 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
post 'settings/generate_api_key', to: 'devise/api_keys#create', as: :generate_api_key
|
post 'settings/generate_api_key', to: 'devise/api_keys#create', as: :generate_api_key
|
||||||
|
|
||||||
get 'points', to: 'points#index'
|
get 'map', to: 'map#index'
|
||||||
|
|
||||||
namespace :api do
|
namespace :api do
|
||||||
namespace :v1 do
|
namespace :v1 do
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,28 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :point do
|
factory :point do
|
||||||
battery_status { 1 }
|
battery_status { 1 }
|
||||||
ping { "MyString" }
|
ping { 'MyString' }
|
||||||
battery { 1 }
|
battery { 1 }
|
||||||
topic { "MyString" }
|
topic { 'MyString' }
|
||||||
altitude { 1 }
|
altitude { 1 }
|
||||||
longitude { "MyString" }
|
longitude { 'MyString' }
|
||||||
velocity { "MyString" }
|
velocity { 'MyString' }
|
||||||
trigger { 1 }
|
trigger { 1 }
|
||||||
bssid { "MyString" }
|
bssid { 'MyString' }
|
||||||
ssid { "MyString" }
|
ssid { 'MyString' }
|
||||||
connection { 1 }
|
connection { 1 }
|
||||||
vertical_accuracy { 1 }
|
vertical_accuracy { 1 }
|
||||||
accuracy { 1 }
|
accuracy { 1 }
|
||||||
timestamp { 1 }
|
timestamp { 1 }
|
||||||
latitude { "MyString" }
|
latitude { 'MyString' }
|
||||||
mode { 1 }
|
mode { 1 }
|
||||||
inrids { "MyString" }
|
inrids { 'MyString' }
|
||||||
in_regions { "MyString" }
|
in_regions { 'MyString' }
|
||||||
raw_data { "" }
|
raw_data { '' }
|
||||||
tracker_id { "MyString" }
|
tracker_id { 'MyString' }
|
||||||
import_id { "" }
|
import_id { '' }
|
||||||
city { nil }
|
city { nil }
|
||||||
country { nil }
|
country { nil }
|
||||||
end
|
end
|
||||||
|
|
|
||||||
113
spec/fixtures/files/google/semantic_history.json
vendored
Normal file
113
spec/fixtures/files/google/semantic_history.json
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
{
|
||||||
|
"timelineObjects": [{
|
||||||
|
"activitySegment": {
|
||||||
|
"startLocation": {
|
||||||
|
},
|
||||||
|
"endLocation": {
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"startTimestamp": "2013-12-01T07:05:14.222Z",
|
||||||
|
"endTimestamp": "2013-12-01T07:11:13.214Z"
|
||||||
|
},
|
||||||
|
"confidence": "LOW",
|
||||||
|
"activities": [{
|
||||||
|
"activityType": "WALKING",
|
||||||
|
"probability": 0.0
|
||||||
|
}, {
|
||||||
|
"activityType": "CYCLING",
|
||||||
|
"probability": 0.0
|
||||||
|
}, {
|
||||||
|
"activityType": "IN_VEHICLE",
|
||||||
|
"probability": 0.0
|
||||||
|
}],
|
||||||
|
"waypointPath": {
|
||||||
|
"waypoints": [{
|
||||||
|
"latE7": 533407440,
|
||||||
|
"lngE7": 837026901
|
||||||
|
}, {
|
||||||
|
"latE7": 533410301,
|
||||||
|
"lngE7": 837051010
|
||||||
|
}],
|
||||||
|
"source": "BACKFILLED",
|
||||||
|
"distanceMeters": 209.65263509609417,
|
||||||
|
"travelMode": "WALK",
|
||||||
|
"confidence": 1.0
|
||||||
|
},
|
||||||
|
"editConfirmationStatus": "NOT_CONFIRMED"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"activitySegment": {
|
||||||
|
"startLocation": {
|
||||||
|
},
|
||||||
|
"endLocation": {
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"startTimestamp": "2013-12-01T07:11:13.214Z",
|
||||||
|
"endTimestamp": "2013-12-01T08:25:17.226Z"
|
||||||
|
},
|
||||||
|
"distance": 3853,
|
||||||
|
"confidence": "LOW",
|
||||||
|
"activities": [{
|
||||||
|
"activityType": "IN_VEHICLE",
|
||||||
|
"probability": 0.0
|
||||||
|
}, {
|
||||||
|
"activityType": "WALKING",
|
||||||
|
"probability": 0.0
|
||||||
|
}, {
|
||||||
|
"activityType": "CYCLING",
|
||||||
|
"probability": 0.0
|
||||||
|
}],
|
||||||
|
"waypointPath": {
|
||||||
|
"waypoints": [{
|
||||||
|
"latE7": 533410301,
|
||||||
|
"lngE7": 837051010
|
||||||
|
}, {
|
||||||
|
"latE7": 533519706,
|
||||||
|
"lngE7": 837596359
|
||||||
|
}],
|
||||||
|
"source": "BACKFILLED",
|
||||||
|
"distanceMeters": 4877.009216418153,
|
||||||
|
"travelMode": "DRIVE",
|
||||||
|
"confidence": 1.0
|
||||||
|
},
|
||||||
|
"editConfirmationStatus": "NOT_CONFIRMED"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"activitySegment": {
|
||||||
|
"startLocation": {
|
||||||
|
},
|
||||||
|
"endLocation": {
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"startTimestamp": "2013-12-01T08:25:17.226Z",
|
||||||
|
"endTimestamp": "2013-12-01T09:11:45.637Z"
|
||||||
|
},
|
||||||
|
"distance": 413,
|
||||||
|
"confidence": "LOW",
|
||||||
|
"activities": [{
|
||||||
|
"activityType": "WALKING",
|
||||||
|
"probability": 0.0
|
||||||
|
}, {
|
||||||
|
"activityType": "CYCLING",
|
||||||
|
"probability": 0.0
|
||||||
|
}, {
|
||||||
|
"activityType": "IN_VEHICLE",
|
||||||
|
"probability": 0.0
|
||||||
|
}],
|
||||||
|
"waypointPath": {
|
||||||
|
"waypoints": [{
|
||||||
|
"latE7": 533519706,
|
||||||
|
"lngE7": 837596359
|
||||||
|
}, {
|
||||||
|
"latE7": 533481369,
|
||||||
|
"lngE7": 837608337
|
||||||
|
}],
|
||||||
|
"source": "BACKFILLED",
|
||||||
|
"distanceMeters": 529.8583466261781,
|
||||||
|
"travelMode": "WALK",
|
||||||
|
"confidence": 1.0
|
||||||
|
},
|
||||||
|
"editConfirmationStatus": "NOT_CONFIRMED"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ RSpec.describe ReverseGeocodingJob, type: :job do
|
||||||
before { stub_const('REVERSE_GEOCODING_ENABLED', false) }
|
before { stub_const('REVERSE_GEOCODING_ENABLED', false) }
|
||||||
|
|
||||||
it 'does not update point' do
|
it 'does not update point' do
|
||||||
expect { perform }.not_to change { point.reload.city }
|
expect { perform }.not_to(change { point.reload.city })
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not call Geocoder' do
|
it 'does not call Geocoder' do
|
||||||
|
|
|
||||||
32
spec/requests/map_spec.rb
Normal file
32
spec/requests/map_spec.rb
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Map', type: :request do
|
||||||
|
before do
|
||||||
|
stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
|
||||||
|
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET /index' do
|
||||||
|
context 'when user signed in' do
|
||||||
|
before do
|
||||||
|
sign_in create(:user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
get map_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user not signed in' do
|
||||||
|
it 'returns http success' do
|
||||||
|
get map_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(302)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -2,31 +2,58 @@
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe 'Points', type: :request do
|
RSpec.describe '/points', type: :request do
|
||||||
before do
|
|
||||||
stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
|
|
||||||
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'GET /index' do
|
describe 'GET /index' do
|
||||||
context 'when user signed in' do
|
before do
|
||||||
|
stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
|
||||||
|
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is not logged in' do
|
||||||
|
it 'redirects to login page' do
|
||||||
|
get points_url
|
||||||
|
expect(response).to redirect_to(new_user_session_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is logged in' do
|
||||||
before do
|
before do
|
||||||
sign_in create(:user)
|
sign_in create(:user)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http success' do
|
it 'renders a successful response' do
|
||||||
get points_path
|
get points_url
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to be_successful
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when user not signed in' do
|
|
||||||
it 'returns http success' do
|
|
||||||
get points_path
|
|
||||||
|
|
||||||
expect(response).to have_http_status(302)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'DELETE /bulk_destroy' do
|
||||||
|
let(:point1) { create(:point) }
|
||||||
|
let(:point2) { create(:point) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in create(:user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'destroys the selected points' do
|
||||||
|
delete bulk_destroy_points_url, params: { point_ids: [point1.id, point2.id] }
|
||||||
|
|
||||||
|
expect(Point.find_by(id: point1.id)).to be_nil
|
||||||
|
expect(Point.find_by(id: point2.id)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a 303 status code' do
|
||||||
|
delete bulk_destroy_points_url, params: { point_ids: [point1.id, point2.id] }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(303)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redirects to the points list' do
|
||||||
|
delete bulk_destroy_points_url, params: { point_ids: [point1.id, point2.id] }
|
||||||
|
|
||||||
|
expect(response).to redirect_to(points_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ paths:
|
||||||
lat: 52.502397
|
lat: 52.502397
|
||||||
lon: 13.356718
|
lon: 13.356718
|
||||||
tid: Swagger
|
tid: Swagger
|
||||||
tst: 1716037633
|
tst: 1716487940
|
||||||
servers:
|
servers:
|
||||||
- url: http://{defaultHost}
|
- url: http://{defaultHost}
|
||||||
variables:
|
variables:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue