@@ -23,7 +100,7 @@ export function handleAreaCreated(areasLayer, layer, apiKey) {
@@ -35,11 +112,14 @@ export function handleAreaCreated(areasLayer, layer, apiKey) {
`;
layer.bindPopup(formHtml, {
- maxWidth: "auto",
- minWidth: 300,
+ maxWidth: 400,
+ minWidth: 384,
+ maxHeight: 600,
closeButton: true,
closeOnClick: false,
- className: 'area-form-popup'
+ className: 'area-form-popup',
+ autoPan: true,
+ keepInView: true
}).openPopup();
areasLayer.addLayer(layer);
@@ -69,7 +149,7 @@ export function handleAreaCreated(areasLayer, layer, apiKey) {
e.stopPropagation();
if (!nameInput.value.trim()) {
- nameInput.classList.add('input-error');
+ nameInput.classList.add('input-error', 'border-error');
return;
}
@@ -106,10 +186,29 @@ export function saveArea(formData, areasLayer, layer, apiKey) {
.then(data => {
layer.closePopup();
layer.bindPopup(`
- Name: ${data.name}
- Radius: ${Math.round(data.radius)} meters
-
[Delete]
- `).openPopup();
+
+
+
${data.name}
+
+
Radius: ${Math.round(data.radius)} meters
+
+
+
+
+ `, {
+ maxWidth: 340,
+ minWidth: 320,
+ className: 'area-info-popup',
+ closeButton: true,
+ closeOnClick: false
+ }).openPopup();
// Add event listener for the delete button
layer.on('popupopen', () => {
@@ -151,6 +250,9 @@ export function deleteArea(id, areasLayer, layer, apiKey) {
}
export function fetchAndDrawAreas(areasLayer, apiKey) {
+ // Add popup styles
+ addPopupStyles();
+
fetch(`/api/v1/areas?api_key=${apiKey}`, {
method: 'GET',
headers: {
@@ -186,20 +288,42 @@ export function fetchAndDrawAreas(areasLayer, apiKey) {
pane: 'areasPane'
});
- // Bind popup content
+ // Bind popup content with proper theme-aware styling
const popupContent = `
-
+
-
${area.name}
-
Radius: ${Math.round(radius)} meters
-
Center: [${lat.toFixed(4)}, ${lng.toFixed(4)}]
-
-
+
${area.name}
+
+
+
+
Radius
+
${Math.round(radius)} meters
+
+
+
Center
+
[${lat.toFixed(4)}, ${lng.toFixed(4)}]
+
+
+
+
+
Area ${area.id}
+
`;
- circle.bindPopup(popupContent);
+ circle.bindPopup(popupContent, {
+ maxWidth: 400,
+ minWidth: 384,
+ className: 'area-info-popup',
+ closeButton: true,
+ closeOnClick: false
+ });
// Add delete button handler when popup opens
circle.on('popupopen', () => {
diff --git a/app/jobs/bulk_visits_suggesting_job.rb b/app/jobs/bulk_visits_suggesting_job.rb
index 54174bca..4384be6a 100644
--- a/app/jobs/bulk_visits_suggesting_job.rb
+++ b/app/jobs/bulk_visits_suggesting_job.rb
@@ -17,6 +17,7 @@ class BulkVisitsSuggestingJob < ApplicationJob
time_chunks = Visits::TimeChunks.new(start_at:, end_at:).call
users.active.find_each do |user|
+ next unless user.safe_settings.visits_suggestions_enabled?
next if user.tracked_points.empty?
schedule_chunked_jobs(user, time_chunks)
diff --git a/app/services/users/safe_settings.rb b/app/services/users/safe_settings.rb
index c549dc88..ab5b2181 100644
--- a/app/services/users/safe_settings.rb
+++ b/app/services/users/safe_settings.rb
@@ -18,7 +18,8 @@ class Users::SafeSettings
'immich_api_key' => nil,
'photoprism_url' => nil,
'photoprism_api_key' => nil,
- 'maps' => { 'distance_unit' => 'km' }
+ 'maps' => { 'distance_unit' => 'km' },
+ 'visits_suggestions_enabled' => 'true'
}.freeze
def initialize(settings = {})
@@ -43,7 +44,8 @@ class Users::SafeSettings
photoprism_url: photoprism_url,
photoprism_api_key: photoprism_api_key,
maps: maps,
- distance_unit: distance_unit
+ distance_unit: distance_unit,
+ visits_suggestions_enabled: visits_suggestions_enabled?
}
end
# rubocop:enable Metrics/MethodLength
@@ -111,4 +113,8 @@ class Users::SafeSettings
def distance_unit
settings.dig('maps', 'distance_unit')
end
+
+ def visits_suggestions_enabled?
+ settings['visits_suggestions_enabled'] == 'true'
+ end
end
diff --git a/app/views/settings/background_jobs/index.html.erb b/app/views/settings/background_jobs/index.html.erb
index ebdaaa2c..ba8c1b53 100644
--- a/app/views/settings/background_jobs/index.html.erb
+++ b/app/views/settings/background_jobs/index.html.erb
@@ -19,7 +19,7 @@
Spamming many new jobs at once is a bad idea. Let them work or clear the queue beforehand.
-
+
Start Reverse Geocoding
@@ -48,6 +48,20 @@
<%= link_to 'Open Dashboard', '/sidekiq', target: '_blank', class: 'btn btn-primary' %>
+
+
+
+
+
Visits suggestions
+
Enable or disable visits suggestions. It's a background task that runs every day at midnight. Disabling it might be useful if you don't want to receive visits suggestions or if you're using the Dawarich iOS app, which has its own visits suggestions.
+
+ <% if current_user.safe_settings.visits_suggestions_enabled? %>
+ <%= link_to 'Disable', settings_path(settings: { 'visits_suggestions_enabled' => false }), method: :patch, data: { confirm: 'Are you sure?', turbo_confirm: 'Are you sure?', turbo_method: :patch }, class: 'btn btn-error' %>
+ <% else %>
+ <%= link_to 'Enable', settings_path(settings: { 'visits_suggestions_enabled' => true }), method: :patch, data: { confirm: 'Are you sure?', turbo_confirm: 'Are you sure?', turbo_method: :patch }, class: 'btn btn-success' %>
+ <% end %>
+
+
diff --git a/spec/jobs/bulk_visits_suggesting_job_spec.rb b/spec/jobs/bulk_visits_suggesting_job_spec.rb
index b4545701..66bf7da6 100644
--- a/spec/jobs/bulk_visits_suggesting_job_spec.rb
+++ b/spec/jobs/bulk_visits_suggesting_job_spec.rb
@@ -102,5 +102,17 @@ RSpec.describe BulkVisitsSuggestingJob, type: :job do
described_class.perform_now(start_at: custom_start, end_at: custom_end)
end
+
+ context 'when visits suggestions are disabled' do
+ before do
+ allow_any_instance_of(Users::SafeSettings).to receive(:visits_suggestions_enabled?).and_return(false)
+ end
+
+ it 'does not schedule jobs' do
+ expect(VisitSuggestingJob).not_to receive(:perform_later)
+
+ described_class.perform_now
+ end
+ end
end
end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 4e34b6af..99844b0a 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -13,6 +13,10 @@ require 'super_diff/rspec-rails'
require 'rake'
Rails.application.load_tasks
+
+# Ensure Devise is properly configured for tests
+require 'devise'
+
# Add additional requires below this line. Rails is not loaded until this point!
Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f }
@@ -32,11 +36,14 @@ RSpec.configure do |config|
config.filter_rails_from_backtrace!
config.include FactoryBot::Syntax::Methods
- config.include Devise::Test::IntegrationHelpers, type: :request
- config.include Devise::Test::IntegrationHelpers, type: :system
config.rswag_dry_run = false
+ config.before(:suite) do
+ # Ensure Rails routes are loaded for Devise
+ Rails.application.reload_routes!
+ end
+
config.before do
ActiveJob::Base.queue_adapter = :test
allow(DawarichSettings).to receive(:store_geodata?).and_return(true)
diff --git a/spec/requests/settings_spec.rb b/spec/requests/settings_spec.rb
index a06d0b40..0d99f03d 100644
--- a/spec/requests/settings_spec.rb
+++ b/spec/requests/settings_spec.rb
@@ -80,7 +80,9 @@ RSpec.describe 'Settings', type: :request do
it 'updates the user settings' do
patch '/settings', params: params
- expect(user.reload.settings).to eq(params[:settings])
+ user.reload
+ expect(user.settings['meters_between_routes']).to eq('1000')
+ expect(user.settings['minutes_between_routes']).to eq('10')
end
context 'when user is inactive' do
diff --git a/spec/services/users/safe_settings_spec.rb b/spec/services/users/safe_settings_spec.rb
index ee18406b..11079920 100644
--- a/spec/services/users/safe_settings_spec.rb
+++ b/spec/services/users/safe_settings_spec.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'rails_helper'
+
RSpec.describe Users::SafeSettings do
describe '#default_settings' do
context 'with default values' do
@@ -24,7 +26,8 @@ RSpec.describe Users::SafeSettings do
photoprism_url: nil,
photoprism_api_key: nil,
maps: { "distance_unit" => "km" },
- distance_unit: 'km'
+ distance_unit: 'km',
+ visits_suggestions_enabled: true
}
)
end
@@ -47,7 +50,8 @@ RSpec.describe Users::SafeSettings do
'immich_api_key' => 'immich-key',
'photoprism_url' => 'https://photoprism.example.com',
'photoprism_api_key' => 'photoprism-key',
- 'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' }
+ 'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' },
+ 'visits_suggestions_enabled' => false
}
end
let(:safe_settings) { described_class.new(settings) }
@@ -69,7 +73,32 @@ RSpec.describe Users::SafeSettings do
"immich_api_key" => "immich-key",
"photoprism_url" => "https://photoprism.example.com",
"photoprism_api_key" => "photoprism-key",
- "maps" => { "name" => "custom", "url" => "https://custom.example.com" }
+ "maps" => { "name" => "custom", "url" => "https://custom.example.com" },
+ "visits_suggestions_enabled" => false
+ }
+ )
+ end
+
+ it 'returns custom default_settings configuration' do
+ expect(safe_settings.default_settings).to eq(
+ {
+ fog_of_war_meters: 100,
+ meters_between_routes: 1000,
+ preferred_map_layer: "Satellite",
+ speed_colored_routes: true,
+ points_rendering_mode: "simplified",
+ minutes_between_routes: 60,
+ time_threshold_minutes: 45,
+ merge_threshold_minutes: 20,
+ live_map_enabled: false,
+ route_opacity: 80,
+ immich_url: "https://immich.example.com",
+ immich_api_key: "immich-key",
+ photoprism_url: "https://photoprism.example.com",
+ photoprism_api_key: "photoprism-key",
+ maps: { "name" => "custom", "url" => "https://custom.example.com" },
+ distance_unit: nil,
+ visits_suggestions_enabled: false
}
)
end
@@ -98,6 +127,7 @@ RSpec.describe Users::SafeSettings do
expect(safe_settings.photoprism_url).to be_nil
expect(safe_settings.photoprism_api_key).to be_nil
expect(safe_settings.maps).to eq({ "distance_unit" => "km" })
+ expect(safe_settings.visits_suggestions_enabled?).to be true
end
end
@@ -118,7 +148,8 @@ RSpec.describe Users::SafeSettings do
'immich_api_key' => 'immich-key',
'photoprism_url' => 'https://photoprism.example.com',
'photoprism_api_key' => 'photoprism-key',
- 'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' }
+ 'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' },
+ 'visits_suggestions_enabled' => false
}
end
@@ -138,6 +169,7 @@ RSpec.describe Users::SafeSettings do
expect(safe_settings.photoprism_url).to eq('https://photoprism.example.com')
expect(safe_settings.photoprism_api_key).to eq('photoprism-key')
expect(safe_settings.maps).to eq({ 'name' => 'custom', 'url' => 'https://custom.example.com' })
+ expect(safe_settings.visits_suggestions_enabled?).to be false
end
end
end
diff --git a/spec/support/devise.rb b/spec/support/devise.rb
index 5d8bf8de..a07f0af9 100644
--- a/spec/support/devise.rb
+++ b/spec/support/devise.rb
@@ -1,22 +1,15 @@
# frozen_string_literal: true
-# https://makandracards.com/makandra/37161-rspec-devise-how-to-sign-in-users-in-request-specs
-
-module DeviseRequestSpecHelpers
- include Warden::Test::Helpers
-
- def sign_in(resource_or_scope, resource = nil)
- resource ||= resource_or_scope
- scope = Devise::Mapping.find_scope!(resource_or_scope)
- login_as(resource, scope:)
- end
-
- def sign_out(resource_or_scope)
- scope = Devise::Mapping.find_scope!(resource_or_scope)
- logout(scope)
- end
-end
+# Standard Devise test helpers configuration for request specs
RSpec.configure do |config|
- config.include DeviseRequestSpecHelpers, type: :request
+ config.include Devise::Test::IntegrationHelpers, type: :request
+ config.include Devise::Test::IntegrationHelpers, type: :system
+
+ # Ensure Devise routes are loaded before request specs
+ config.before(:each, type: :request) do
+ # Reload routes to ensure Devise mappings are available
+ Rails.application.reload_routes! unless @routes_reloaded
+ @routes_reloaded = true
+ end
end
diff --git a/spec/swagger/api/v1/countries/visited_cities_spec.rb b/spec/swagger/api/v1/countries/visited_cities_spec.rb
index 61a7fa43..b0de92d8 100644
--- a/spec/swagger/api/v1/countries/visited_cities_spec.rb
+++ b/spec/swagger/api/v1/countries/visited_cities_spec.rb
@@ -17,16 +17,20 @@ RSpec.describe 'Api::V1::Countries::VisitedCities', type: :request do
description: 'Your API authentication key'
parameter name: :start_at,
in: :query,
- type: :string,
- format: 'date-time',
+ schema: {
+ type: :string,
+ format: :date
+ },
required: true,
description: 'Start date in YYYY-MM-DD format',
example: '2023-01-01'
parameter name: :end_at,
in: :query,
- type: :string,
- format: 'date-time',
+ schema: {
+ type: :string,
+ format: :date
+ },
required: true,
description: 'End date in YYYY-MM-DD format',
example: '2023-12-31'
diff --git a/spec/swagger/api/v1/health_controller_spec.rb b/spec/swagger/api/v1/health_controller_spec.rb
index 7305521f..b395fd24 100644
--- a/spec/swagger/api/v1/health_controller_spec.rb
+++ b/spec/swagger/api/v1/health_controller_spec.rb
@@ -14,14 +14,18 @@ describe 'Health API', type: :request do
}
header 'X-Dawarich-Response',
- type: :string,
+ schema: {
+ type: :string,
+ example: 'Hey, I\'m alive!'
+ },
required: true,
- example: 'Hey, I\'m alive!',
description: "Depending on the authentication status of the request, the response will be different. If the request is authenticated, the response will be 'Hey, I'm alive and authenticated!'. If the request is not authenticated, the response will be 'Hey, I'm alive!'."
header 'X-Dawarich-Version',
- type: :string,
+ schema: {
+ type: :string,
+ example: '1.0.0'
+ },
required: true,
- example: '1.0.0',
description: 'The version of the application, for example: 1.0.0'
run_test!
diff --git a/spec/swagger/api/v1/overland/batches_controller_spec.rb b/spec/swagger/api/v1/overland/batches_controller_spec.rb
index 4ba2e0d3..b626c56f 100644
--- a/spec/swagger/api/v1/overland/batches_controller_spec.rb
+++ b/spec/swagger/api/v1/overland/batches_controller_spec.rb
@@ -40,99 +40,112 @@ describe 'Overland Batches API', type: :request do
parameter name: :locations, in: :body, schema: {
type: :object,
properties: {
- type: { type: :string, example: 'Feature' },
- geometry: {
- type: :object,
- properties: {
- type: { type: :string, example: 'Point' },
- coordinates: { type: :array, example: [13.356718, 52.502397] }
+ locations: {
+ type: :array,
+ items: {
+ type: :object,
+ properties: {
+ type: { type: :string, example: 'Feature' },
+ geometry: {
+ type: :object,
+ properties: {
+ type: { type: :string, example: 'Point' },
+ coordinates: {
+ type: :array,
+ items: { type: :number },
+ example: [13.356718, 52.502397]
+ }
+ }
+ },
+ properties: {
+ type: :object,
+ properties: {
+ timestamp: {
+ type: :string,
+ example: '2021-06-01T12:00:00Z',
+ description: 'Timestamp in ISO 8601 format'
+ },
+ altitude: {
+ type: :number,
+ example: 0,
+ description: 'Altitude in meters'
+ },
+ speed: {
+ type: :number,
+ example: 0,
+ description: 'Speed in meters per second'
+ },
+ horizontal_accuracy: {
+ type: :number,
+ example: 0,
+ description: 'Horizontal accuracy in meters'
+ },
+ vertical_accuracy: {
+ type: :number,
+ example: 0,
+ description: 'Vertical accuracy in meters'
+ },
+ motion: {
+ type: :array,
+ items: { type: :string },
+ example: %w[walking running driving cycling stationary],
+ description: 'Motion type, for example: automotive_navigation, fitness, other_navigation or other'
+ },
+ activity: {
+ type: :string,
+ example: 'unknown',
+ description: 'Activity type, for example: automotive_navigation, fitness, other_navigation or other'
+ },
+ desired_accuracy: {
+ type: :number,
+ example: 0,
+ description: 'Desired accuracy in meters'
+ },
+ deferred: {
+ type: :number,
+ example: 0,
+ description: 'the distance in meters to defer location updates'
+ },
+ significant_change: {
+ type: :string,
+ example: 'disabled',
+ description: 'a significant change mode, disabled, enabled or exclusive'
+ },
+ locations_in_payload: {
+ type: :number,
+ example: 1,
+ description: 'the number of locations in the payload'
+ },
+ device_id: {
+ type: :string,
+ example: 'iOS device #166',
+ description: 'the device id'
+ },
+ unique_id: {
+ type: :string,
+ example: '1234567890',
+ description: 'the device\'s Unique ID as set by Apple'
+ },
+ wifi: {
+ type: :string,
+ example: 'unknown',
+ description: 'the WiFi network name'
+ },
+ battery_state: {
+ type: :string,
+ example: 'unknown',
+ description: 'the battery state, unknown, unplugged, charging or full'
+ },
+ battery_level: {
+ type: :number,
+ example: 0,
+ description: 'the battery level percentage, from 0 to 1'
+ }
+ }
+ }
+ },
+ required: %w[geometry properties]
}
- },
- properties: {
- type: :object,
- properties: {
- timestamp: {
- type: :string,
- example: '2021-06-01T12:00:00Z',
- description: 'Timestamp in ISO 8601 format'
- },
- altitude: {
- type: :number,
- example: 0,
- description: 'Altitude in meters'
- },
- speed: {
- type: :number,
- example: 0,
- description: 'Speed in meters per second'
- },
- horizontal_accuracy: {
- type: :number,
- example: 0,
- description: 'Horizontal accuracy in meters'
- },
- vertical_accuracy: {
- type: :number,
- example: 0,
- description: 'Vertical accuracy in meters'
- },
- motion: {
- type: :array,
- example: %w[walking running driving cycling stationary],
- description: 'Motion type, for example: automotive_navigation, fitness, other_navigation or other'
- },
- activity: {
- type: :string,
- example: 'unknown',
- description: 'Activity type, for example: automotive_navigation, fitness, other_navigation or other'
- },
- desired_accuracy: {
- type: :number,
- example: 0,
- description: 'Desired accuracy in meters'
- },
- deferred: {
- type: :number,
- example: 0,
- description: 'the distance in meters to defer location updates'
- },
- significant_change: {
- type: :string,
- example: 'disabled',
- description: 'a significant change mode, disabled, enabled or exclusive'
- },
- locations_in_payload: {
- type: :number,
- example: 1,
- description: 'the number of locations in the payload'
- },
- device_id: {
- type: :string,
- example: 'iOS device #166',
- description: 'the device id'
- },
- unique_id: {
- type: :string,
- example: '1234567890',
- description: 'the device\'s Unique ID as set by Apple'
- },
- wifi: {
- type: :string,
- example: 'unknown',
- description: 'the WiFi network name'
- },
- battery_state: {
- type: :string,
- example: 'unknown',
- description: 'the battery state, unknown, unplugged, charging or full'
- },
- battery_level: {
- type: :number,
- example: 0,
- description: 'the battery level percentage, from 0 to 1'
- }
- },
- required: %w[geometry properties]
}
}
}
diff --git a/spec/swagger/api/v1/owntracks/points_controller_spec.rb b/spec/swagger/api/v1/owntracks/points_controller_spec.rb
index 00157df8..5159a302 100644
--- a/spec/swagger/api/v1/owntracks/points_controller_spec.rb
+++ b/spec/swagger/api/v1/owntracks/points_controller_spec.rb
@@ -43,11 +43,11 @@ describe 'OwnTracks Points API', type: :request do
lon: { type: :number, description: 'Longitude coordinate' },
acc: { type: :number, description: 'Accuracy of position in meters' },
bs: { type: :number, description: 'Battery status (0=unknown, 1=unplugged, 2=charging, 3=full)' },
- inrids: { type: :array, description: 'Array of region IDs device is currently in' },
+ inrids: { type: :array, items: { type: :string }, description: 'Array of region IDs device is currently in' },
BSSID: { type: :string, description: 'Connected WiFi access point MAC address' },
SSID: { type: :string, description: 'Connected WiFi network name' },
vac: { type: :number, description: 'Vertical accuracy in meters' },
- inregions: { type: :array, description: 'Array of region names device is currently in' },
+ inregions: { type: :array, items: { type: :string }, description: 'Array of region names device is currently in' },
lat: { type: :number, description: 'Latitude coordinate' },
topic: { type: :string, description: 'MQTT topic in format owntracks/user/device' },
t: { type: :string, description: 'Type of message (p=position, c=circle, etc)' },
@@ -63,7 +63,7 @@ describe 'OwnTracks Points API', type: :request do
isotst: { type: :string, description: 'ISO 8601 timestamp of the location fix' },
disptst: { type: :string, description: 'Human-readable timestamp of the location fix' }
},
- required: %w[owntracks/jane]
+ required: %w[lat lon tst _type]
}
parameter name: :api_key, in: :query, type: :string, required: true, description: 'API Key'
diff --git a/spec/swagger/api/v1/points_controller_spec.rb b/spec/swagger/api/v1/points_controller_spec.rb
index 7450df45..2b5fe369 100644
--- a/spec/swagger/api/v1/points_controller_spec.rb
+++ b/spec/swagger/api/v1/points_controller_spec.rb
@@ -39,8 +39,8 @@ describe 'Points API', type: :request do
timestamp: { type: :number },
latitude: { type: :number },
mode: { type: :number },
- inrids: { type: :array },
- in_regions: { type: :array },
+ inrids: { type: :array, items: { type: :string } },
+ in_regions: { type: :array, items: { type: :string } },
raw_data: { type: :string },
import_id: { type: :string },
city: { type: :string },
diff --git a/spec/swagger/api/v1/settings_controller_spec.rb b/spec/swagger/api/v1/settings_controller_spec.rb
index aecba56b..e9716d12 100644
--- a/spec/swagger/api/v1/settings_controller_spec.rb
+++ b/spec/swagger/api/v1/settings_controller_spec.rb
@@ -7,12 +7,22 @@ describe 'Settings API', type: :request do
patch 'Updates user settings' do
request_body_example value: {
'settings': {
- 'route_opacity': 0.3,
- 'meters_between_routes': 100,
- 'minutes_between_routes': 100,
- 'fog_of_war_meters': 100,
- 'time_threshold_minutes': 100,
- 'merge_threshold_minutes': 100
+ 'route_opacity': 60,
+ 'meters_between_routes': 500,
+ 'minutes_between_routes': 30,
+ 'fog_of_war_meters': 50,
+ 'time_threshold_minutes': 30,
+ 'merge_threshold_minutes': 15,
+ 'preferred_map_layer': 'OpenStreetMap',
+ 'speed_colored_routes': false,
+ 'points_rendering_mode': 'raw',
+ 'live_map_enabled': true,
+ 'immich_url': 'https://immich.example.com',
+ 'immich_api_key': 'your-immich-api-key',
+ 'photoprism_url': 'https://photoprism.example.com',
+ 'photoprism_api_key': 'your-photoprism-api-key',
+ 'maps': { 'distance_unit': 'km' },
+ 'visits_suggestions_enabled': true
}
}
tags 'Settings'
@@ -22,31 +32,95 @@ describe 'Settings API', type: :request do
properties: {
route_opacity: {
type: :number,
- example: 0.3,
- description: 'the opacity of the route, float between 0 and 1'
+ example: 60,
+ description: 'Route opacity percentage (0-100)'
},
meters_between_routes: {
type: :number,
- example: 100,
- description: 'the distance between routes in meters'
+ example: 500,
+ description: 'Minimum distance between routes in meters'
},
minutes_between_routes: {
type: :number,
- example: 100,
- description: 'the time between routes in minutes'
+ example: 30,
+ description: 'Minimum time between routes in minutes'
},
fog_of_war_meters: {
type: :number,
- example: 100,
- description: 'the fog of war distance in meters'
+ example: 50,
+ description: 'Fog of war radius in meters'
+ },
+ time_threshold_minutes: {
+ type: :number,
+ example: 30,
+ description: 'Time threshold for grouping points in minutes'
+ },
+ merge_threshold_minutes: {
+ type: :number,
+ example: 15,
+ description: 'Threshold for merging nearby points in minutes'
+ },
+ preferred_map_layer: {
+ type: :string,
+ example: 'OpenStreetMap',
+ description: 'Preferred map layer/tile provider'
+ },
+ speed_colored_routes: {
+ type: :boolean,
+ example: false,
+ description: 'Whether to color routes based on speed'
+ },
+ points_rendering_mode: {
+ type: :string,
+ example: 'raw',
+ description: 'How to render points on the map (raw, heatmap, etc.)'
+ },
+ live_map_enabled: {
+ type: :boolean,
+ example: true,
+ description: 'Whether live map updates are enabled'
+ },
+ immich_url: {
+ type: :string,
+ example: 'https://immich.example.com',
+ description: 'Immich server URL for photo integration'
+ },
+ immich_api_key: {
+ type: :string,
+ example: 'your-immich-api-key',
+ description: 'API key for Immich photo service'
+ },
+ photoprism_url: {
+ type: :string,
+ example: 'https://photoprism.example.com',
+ description: 'PhotoPrism server URL for photo integration'
+ },
+ photoprism_api_key: {
+ type: :string,
+ example: 'your-photoprism-api-key',
+ description: 'API key for PhotoPrism photo service'
+ },
+ maps: {
+ type: :object,
+ properties: {
+ distance_unit: {
+ type: :string,
+ example: 'km',
+ description: 'Distance unit preference (km or miles)'
+ }
+ },
+ description: 'Map-related settings'
+ },
+ visits_suggestions_enabled: {
+ type: :boolean,
+ example: true,
+ description: 'Whether visit suggestions are enabled'
}
- },
- optional: %w[route_opacity meters_between_routes minutes_between_routes fog_of_war_meters
- time_threshold_minutes merge_threshold_minutes]
+ }
}
parameter name: :api_key, in: :query, type: :string, required: true, description: 'API Key'
response '200', 'settings updated' do
- let(:settings) { { settings: { route_opacity: 0.3 } } }
+ let(:settings) { { settings: { route_opacity: 60 } } }
let(:api_key) { create(:user).api_key }
run_test!
@@ -65,27 +139,91 @@ describe 'Settings API', type: :request do
properties: {
route_opacity: {
type: :string,
- example: 0.3,
- description: 'the opacity of the route, float between 0 and 1'
+ example: '60',
+ description: 'Route opacity percentage (0-100)'
},
meters_between_routes: {
type: :string,
- example: 100,
- description: 'the distance between routes in meters'
+ example: '500',
+ description: 'Minimum distance between routes in meters'
},
minutes_between_routes: {
type: :string,
- example: 100,
- description: 'the time between routes in minutes'
+ example: '30',
+ description: 'Minimum time between routes in minutes'
},
fog_of_war_meters: {
type: :string,
- example: 100,
- description: 'the fog of war distance in meters'
+ example: '50',
+ description: 'Fog of war radius in meters'
+ },
+ time_threshold_minutes: {
+ type: :string,
+ example: '30',
+ description: 'Time threshold for grouping points in minutes'
+ },
+ merge_threshold_minutes: {
+ type: :string,
+ example: '15',
+ description: 'Threshold for merging nearby points in minutes'
+ },
+ preferred_map_layer: {
+ type: :string,
+ example: 'OpenStreetMap',
+ description: 'Preferred map layer/tile provider'
+ },
+ speed_colored_routes: {
+ type: :boolean,
+ example: false,
+ description: 'Whether to color routes based on speed'
+ },
+ points_rendering_mode: {
+ type: :string,
+ example: 'raw',
+ description: 'How to render points on the map (raw, heatmap, etc.)'
+ },
+ live_map_enabled: {
+ type: :boolean,
+ example: true,
+ description: 'Whether live map updates are enabled'
+ },
+ immich_url: {
+ type: :string,
+ example: 'https://immich.example.com',
+ description: 'Immich server URL for photo integration'
+ },
+ immich_api_key: {
+ type: :string,
+ example: 'your-immich-api-key',
+ description: 'API key for Immich photo service'
+ },
+ photoprism_url: {
+ type: :string,
+ example: 'https://photoprism.example.com',
+ description: 'PhotoPrism server URL for photo integration'
+ },
+ photoprism_api_key: {
+ type: :string,
+ example: 'your-photoprism-api-key',
+ description: 'API key for PhotoPrism photo service'
+ },
+ maps: {
+ type: :object,
+ properties: {
+ distance_unit: {
+ type: :string,
+ example: 'km',
+ description: 'Distance unit preference (km or miles)'
+ }
+ },
+ description: 'Map-related settings'
+ },
+ visits_suggestions_enabled: {
+ type: :boolean,
+ example: true,
+ description: 'Whether visit suggestions are enabled'
}
- },
- required: %w[route_opacity meters_between_routes minutes_between_routes fog_of_war_meters
- time_threshold_minutes merge_threshold_minutes]
+ }
}
}
diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml
index a58bcb10..7a65546f 100644
--- a/swagger/v1/swagger.yaml
+++ b/swagger/v1/swagger.yaml
@@ -141,20 +141,20 @@ paths:
type: string
- name: start_at
in: query
- format: date-time
+ schema:
+ type: string
+ format: date
required: true
description: Start date in YYYY-MM-DD format
example: '2023-01-01'
- schema:
- type: string
- name: end_at
in: query
- format: date-time
+ schema:
+ type: string
+ format: date
required: true
description: End date in YYYY-MM-DD format
example: '2023-12-31'
- schema:
- type: string
responses:
'200':
description: cities found
@@ -231,17 +231,19 @@ paths:
description: Healthy
headers:
X-Dawarich-Response:
- type: string
+ schema:
+ type: string
+ example: Hey, I'm alive!
required: true
- example: Hey, I'm alive!
description: Depending on the authentication status of the request,
the response will be different. If the request is authenticated, the
response will be 'Hey, I'm alive and authenticated!'. If the request
is not authenticated, the response will be 'Hey, I'm alive!'.
X-Dawarich-Version:
- type: string
+ schema:
+ type: string
+ example: 1.0.0
required: true
- example: 1.0.0
description: 'The version of the application, for example: 1.0.0'
content:
application/json:
@@ -273,99 +275,109 @@ paths:
schema:
type: object
properties:
- type:
- type: string
- example: Feature
- geometry:
- type: object
- properties:
- type:
- type: string
- example: Point
- coordinates:
- type: array
- example:
- - 13.356718
- - 52.502397
- properties:
- type: object
- properties:
- timestamp:
- type: string
- example: '2021-06-01T12:00:00Z'
- description: Timestamp in ISO 8601 format
- altitude:
- type: number
- example: 0
- description: Altitude in meters
- speed:
- type: number
- example: 0
- description: Speed in meters per second
- horizontal_accuracy:
- type: number
- example: 0
- description: Horizontal accuracy in meters
- vertical_accuracy:
- type: number
- example: 0
- description: Vertical accuracy in meters
- motion:
- type: array
- example:
- - walking
- - running
- - driving
- - cycling
- - stationary
- description: 'Motion type, for example: automotive_navigation,
- fitness, other_navigation or other'
- activity:
- type: string
- example: unknown
- description: 'Activity type, for example: automotive_navigation,
- fitness, other_navigation or other'
- desired_accuracy:
- type: number
- example: 0
- description: Desired accuracy in meters
- deferred:
- type: number
- example: 0
- description: the distance in meters to defer location updates
- significant_change:
- type: string
- example: disabled
- description: a significant change mode, disabled, enabled or
- exclusive
- locations_in_payload:
- type: number
- example: 1
- description: the number of locations in the payload
- device_id:
- type: string
- example: 'iOS device #166'
- description: the device id
- unique_id:
- type: string
- example: '1234567890'
- description: the device's Unique ID as set by Apple
- wifi:
- type: string
- example: unknown
- description: the WiFi network name
- battery_state:
- type: string
- example: unknown
- description: the battery state, unknown, unplugged, charging
- or full
- battery_level:
- type: number
- example: 0
- description: the battery level percentage, from 0 to 1
- required:
- - geometry
- - properties
+ locations:
+ type: array
+ items:
+ type: object
+ properties:
+ type:
+ type: string
+ example: Feature
+ geometry:
+ type: object
+ properties:
+ type:
+ type: string
+ example: Point
+ coordinates:
+ type: array
+ items:
+ type: number
+ example:
+ - 13.356718
+ - 52.502397
+ properties:
+ type: object
+ properties:
+ timestamp:
+ type: string
+ example: '2021-06-01T12:00:00Z'
+ description: Timestamp in ISO 8601 format
+ altitude:
+ type: number
+ example: 0
+ description: Altitude in meters
+ speed:
+ type: number
+ example: 0
+ description: Speed in meters per second
+ horizontal_accuracy:
+ type: number
+ example: 0
+ description: Horizontal accuracy in meters
+ vertical_accuracy:
+ type: number
+ example: 0
+ description: Vertical accuracy in meters
+ motion:
+ type: array
+ items:
+ type: string
+ example:
+ - walking
+ - running
+ - driving
+ - cycling
+ - stationary
+ description: 'Motion type, for example: automotive_navigation,
+ fitness, other_navigation or other'
+ activity:
+ type: string
+ example: unknown
+ description: 'Activity type, for example: automotive_navigation,
+ fitness, other_navigation or other'
+ desired_accuracy:
+ type: number
+ example: 0
+ description: Desired accuracy in meters
+ deferred:
+ type: number
+ example: 0
+ description: the distance in meters to defer location
+ updates
+ significant_change:
+ type: string
+ example: disabled
+ description: a significant change mode, disabled, enabled
+ or exclusive
+ locations_in_payload:
+ type: number
+ example: 1
+ description: the number of locations in the payload
+ device_id:
+ type: string
+ example: 'iOS device #166'
+ description: the device id
+ unique_id:
+ type: string
+ example: '1234567890'
+ description: the device's Unique ID as set by Apple
+ wifi:
+ type: string
+ example: unknown
+ description: the WiFi network name
+ battery_state:
+ type: string
+ example: unknown
+ description: the battery state, unknown, unplugged, charging
+ or full
+ battery_level:
+ type: number
+ example: 0
+ description: the battery level percentage, from 0 to 1
+ required:
+ - geometry
+ - properties
examples:
'0':
summary: Creates a batch of points
@@ -433,6 +445,8 @@ paths:
3=full)
inrids:
type: array
+ items:
+ type: string
description: Array of region IDs device is currently in
BSSID:
type: string
@@ -445,6 +459,8 @@ paths:
description: Vertical accuracy in meters
inregions:
type: array
+ items:
+ type: string
description: Array of region names device is currently in
lat:
type: number
@@ -489,7 +505,10 @@ paths:
type: string
description: Human-readable timestamp of the location fix
required:
- - owntracks/jane
+ - lat
+ - lon
+ - tst
+ - _type
examples:
'0':
summary: Creates a point
@@ -805,8 +824,12 @@ paths:
type: number
inrids:
type: array
+ items:
+ type: string
in_regions:
type: array
+ items:
+ type: string
raw_data:
type: string
import_id:
@@ -982,38 +1005,94 @@ paths:
properties:
route_opacity:
type: number
- example: 0.3
- description: the opacity of the route, float between 0 and 1
+ example: 60
+ description: Route opacity percentage (0-100)
meters_between_routes:
type: number
- example: 100
- description: the distance between routes in meters
+ example: 500
+ description: Minimum distance between routes in meters
minutes_between_routes:
type: number
- example: 100
- description: the time between routes in minutes
+ example: 30
+ description: Minimum time between routes in minutes
fog_of_war_meters:
type: number
- example: 100
- description: the fog of war distance in meters
- optional:
- - route_opacity
- - meters_between_routes
- - minutes_between_routes
- - fog_of_war_meters
- - time_threshold_minutes
- - merge_threshold_minutes
+ example: 50
+ description: Fog of war radius in meters
+ time_threshold_minutes:
+ type: number
+ example: 30
+ description: Time threshold for grouping points in minutes
+ merge_threshold_minutes:
+ type: number
+ example: 15
+ description: Threshold for merging nearby points in minutes
+ preferred_map_layer:
+ type: string
+ example: OpenStreetMap
+ description: Preferred map layer/tile provider
+ speed_colored_routes:
+ type: boolean
+ example: false
+ description: Whether to color routes based on speed
+ points_rendering_mode:
+ type: string
+ example: raw
+ description: How to render points on the map (raw, heatmap, etc.)
+ live_map_enabled:
+ type: boolean
+ example: true
+ description: Whether live map updates are enabled
+ immich_url:
+ type: string
+ example: https://immich.example.com
+ description: Immich server URL for photo integration
+ immich_api_key:
+ type: string
+ example: your-immich-api-key
+ description: API key for Immich photo service
+ photoprism_url:
+ type: string
+ example: https://photoprism.example.com
+ description: PhotoPrism server URL for photo integration
+ photoprism_api_key:
+ type: string
+ example: your-photoprism-api-key
+ description: API key for PhotoPrism photo service
+ maps:
+ type: object
+ properties:
+ distance_unit:
+ type: string
+ example: km
+ description: Distance unit preference (km or miles)
+ description: Map-related settings
+ visits_suggestions_enabled:
+ type: boolean
+ example: true
+ description: Whether visit suggestions are enabled
examples:
'0':
summary: Updates user settings
value:
settings:
- route_opacity: 0.3
- meters_between_routes: 100
- minutes_between_routes: 100
- fog_of_war_meters: 100
- time_threshold_minutes: 100
- merge_threshold_minutes: 100
+ route_opacity: 60
+ meters_between_routes: 500
+ minutes_between_routes: 30
+ fog_of_war_meters: 50
+ time_threshold_minutes: 30
+ merge_threshold_minutes: 15
+ preferred_map_layer: OpenStreetMap
+ speed_colored_routes: false
+ points_rendering_mode: raw
+ live_map_enabled: true
+ immich_url: https://immich.example.com
+ immich_api_key: your-immich-api-key
+ photoprism_url: https://photoprism.example.com
+ photoprism_api_key: your-photoprism-api-key
+ maps:
+ distance_unit: km
+ visits_suggestions_enabled: true
get:
summary: Retrieves user settings
tags:
@@ -1038,28 +1117,73 @@ paths:
properties:
route_opacity:
type: string
- example: 0.3
- description: the opacity of the route, float between 0 and
- 1
+ example: '60'
+ description: Route opacity percentage (0-100)
meters_between_routes:
type: string
- example: 100
- description: the distance between routes in meters
+ example: '500'
+ description: Minimum distance between routes in meters
minutes_between_routes:
type: string
- example: 100
- description: the time between routes in minutes
+ example: '30'
+ description: Minimum time between routes in minutes
fog_of_war_meters:
type: string
- example: 100
- description: the fog of war distance in meters
- required:
- - route_opacity
- - meters_between_routes
- - minutes_between_routes
- - fog_of_war_meters
- - time_threshold_minutes
- - merge_threshold_minutes
+ example: '50'
+ description: Fog of war radius in meters
+ time_threshold_minutes:
+ type: string
+ example: '30'
+ description: Time threshold for grouping points in minutes
+ merge_threshold_minutes:
+ type: string
+ example: '15'
+ description: Threshold for merging nearby points in minutes
+ preferred_map_layer:
+ type: string
+ example: OpenStreetMap
+ description: Preferred map layer/tile provider
+ speed_colored_routes:
+ type: boolean
+ example: false
+ description: Whether to color routes based on speed
+ points_rendering_mode:
+ type: string
+ example: raw
+ description: How to render points on the map (raw, heatmap,
+ etc.)
+ live_map_enabled:
+ type: boolean
+ example: true
+ description: Whether live map updates are enabled
+ immich_url:
+ type: string
+ example: https://immich.example.com
+ description: Immich server URL for photo integration
+ immich_api_key:
+ type: string
+ example: your-immich-api-key
+ description: API key for Immich photo service
+ photoprism_url:
+ type: string
+ example: https://photoprism.example.com
+ description: PhotoPrism server URL for photo integration
+ photoprism_api_key:
+ type: string
+ example: your-photoprism-api-key
+ description: API key for PhotoPrism photo service
+ maps:
+ type: object
+ properties:
+ distance_unit:
+ type: string
+ example: km
+ description: Distance unit preference (km or miles)
+ description: Map-related settings
+ visits_suggestions_enabled:
+ type: boolean
+ example: true
+ description: Whether visit suggestions are enabled
"/api/v1/stats":
get:
summary: Retrieves all stats